如何在Python非主线程中设置F_NOTIFY?Linux目录监听问题
问题:Ubuntu 16 + Python 3.5.2下非主线程调用fcntl设置F_NOTIFY无信号触发的原因
我在Linux(Ubuntu 16,Python 3.5.2)中尝试用F_NOTIFY监听目录,发现只有主线程调用fcntl时能正常触发信号,非主线程调用则完全没有信号产生。同时有两个疑问:
- Python的fcntl文档里完全没提到线程相关的限制,为什么会出现这种情况?
- 如果fcntl执行失败,为什么没有抛出异常?
测试代码如下:
# 运行前先创建目录"a"。测试时在"a"目录下创建文件即可。 import fcntl, signal, os, threading pipe_r, pipe_w = os.pipe() def Unsafe_Handler(s,f): os.write(pipe_w, b' ') signal.signal(signal.SIGIO, Unsafe_Handler) signal.siginterrupt(signal.SIGIO, False) def Handler(): while True: os.read(pipe_r, 1) print("收到信号") threading.Thread(target = Handler).start() NOTIF_FLAGS = fcntl.DN_MODIFY | fcntl.DN_CREATE | fcntl.DN_MULTISHOT def Watch_Dir(dn): fd = os.open(dn, os.O_RDONLY) fcntl.fcntl(fd, fcntl.F_SETSIG, 0) fcntl.fcntl(fd, fcntl.F_NOTIFY, NOTIF_FLAGS) print('正在监听目录 "%s", 文件描述符fd=%d' % (dn, fd)) return fd def Init(): fd = Watch_Dir('a') # 这段代码可以正常工作 #Init() # 这段代码无法正常工作 t = threading.Thread(target = Init) t.start() t.join() print("等待信号中...") while True: signal.pause()
Python中信号处理为何需要线程
Handler线程是必需的,原因如下:
- 通过
signal.signal绑定的信号处理器仅能安全地执行向管道写入这类操作(避免信号处理时的线程安全问题)。 - 主线程无法承担实际业务工作,原因有两点:
- 主线程中多数阻塞调用会阻碍信号的处理逻辑;
- 若仅依赖
signal.pause()等待信号,存在信号恰好到达signal.pause()调用之前的竞态条件,可能导致主线程卡死。
为何在非主线程调用fcntl很有用
这主要是因为如前所述,主线程无法执行实际工作。(实际应用中我的业务处理器需要调用fcntl,因此编写了这个简化示例。)
问题解答
1. 非主线程调用fcntl无信号的核心原因
Linux的F_NOTIFY机制依赖内核向目标发送SIGIO信号,而信号的投递目标由文件描述符的所有者决定:
- 当在子线程中打开文件描述符并设置
F_NOTIFY时,默认情况下该fd的所有者是当前子线程的ID,而非整个进程ID; - 子线程执行完
Init()后就会退出,此时内核发送的SIGIO信号没有对应的接收线程,自然会被丢弃,无法触发信号处理器。 - 而主线程调用时,fd的所有者是主线程ID,信号能被正常接收并处理。
解决方法是在设置F_NOTIFY前,主动将fd的所有者设置为进程ID,让内核把信号发送给整个进程(最终由主线程处理):
def Watch_Dir(dn): fd = os.open(dn, os.O_RDONLY) # 设置fd的所有者为当前进程ID fcntl.fcntl(fd, fcntl.F_SETOWN, os.getpid()) fcntl.fcntl(fd, fcntl.F_SETSIG, 0) fcntl.fcntl(fd, fcntl.F_NOTIFY, NOTIF_FLAGS) print('正在监听目录 "%s", 文件描述符fd=%d' % (dn, fd)) return fd
2. 为何fcntl未抛出异常
你的代码中fcntl调用实际上是执行成功的——只是信号投递目标错误导致没有触发信号,而非系统调用失败。Python的fcntl模块会在系统调用返回错误时自动抛出OSError异常,如果没有异常,说明所有fcntl操作都完成了。
内容的提问来源于stack exchange,提问作者personal_cloud
相关产品推荐
相关产品推荐

