如何给线程中的系统调用添加超时?以fcntl.flock为例
问题
希望调用fcntl.flock()给文件加锁并设置超时。参考《Timeout on a function call》中的方案,用contextmanager封装了通过Unix信号实现超时的代码:
@contextmanager def doTimeout(seconds): """Creates a "with" context that times out after the specified time.""" def timeout_handler(signum, frame): pass original_handler = signal.signal(signal.SIGALRM, timeout_handler) try: signal.alarm(seconds) yield finally: signal.alarm(0) signal.signal(signal.SIGALRM, original_handler)
使用方式如下:
with doTimeout(timeout): try: fcntl.flock(self.file, fcntl.LOCK_EX) self.locked = True return True except (OSError, IOError) as e: if e.errno == errno.EINTR: return False raise
该方案在主线程中运行正常,但由于只有主线程能捕获信号,无法在其他线程中使用。是否有办法在子线程中实现?目前备选方案是轮询锁状态后休眠,或启动子进程,但均不理想,是否有更优方案?
解决方案
可以用非阻塞锁+select等待的方式实现,完全避开信号机制,子线程也能正常工作,比轮询更高效。
核心思路:
- 先用
fcntl.LOCK_EX | fcntl.LOCK_NB以非阻塞模式尝试加锁,成功则直接返回 - 若加锁失败(返回
EAGAIN/EWOULDBLOCK),用select.select()等待文件描述符变为可写(排他锁等待时,文件描述符会处于可写状态),同时设置超时时间
实现代码如下:
import fcntl import select import errno def lock_with_timeout(file_obj, timeout): """给文件对象加排他锁,支持超时,可在子线程中使用""" try: # 非阻塞模式尝试加锁 fcntl.flock(file_obj, fcntl.LOCK_EX | fcntl.LOCK_NB) file_obj.locked = True return True except (OSError, IOError) as e: if e.errno not in (errno.EAGAIN, errno.EWOULDBLOCK): # 非锁占用类错误,直接抛出 raise # 等待文件可写,同时设置超时 try: ready, _, _ = select.select([], [file_obj], [], timeout) if ready: # 文件可写时再次加锁 fcntl.flock(file_obj, fcntl.LOCK_EX) file_obj.locked = True return True else: # 超时返回 return False except (OSError, IOError) as e: if e.errno == errno.EINTR: # 被信号中断,视为超时 return False raise
方案优势
- 不依赖信号机制,子线程可正常使用
- 基于
select的等待是内核级别的,比轮询休眠更高效,无CPU资源浪费 - 逻辑简洁,无需额外线程/进程开销
使用示例:
if lock_with_timeout(self.file, timeout): # 加锁成功后的业务逻辑 else: # 超时处理逻辑
内容的提问来源于stack exchange,提问作者Edward Falk
相关产品推荐
相关产品推荐

