存疑
RQ涉及一部分OS调用的代码,涉及进程和信号处理,如下代码引起了我的注意
def fork_and_perform_job(self, job):
child_pid = os.fork()
if child_pid == 0:
self.main_work_horse(job)
else:
self._horse_pid = child_pid
self.procline('Forked %d at %d' % (child_pid, time.time()))
while True:
try:
os.waitpid(child_pid, 0)
break
except OSError as e:
# In case we encountered an OSError due to EINTR (which is
# caused by a SIGINT or SIGTERM signal during
# os.waitpid()), we simply ignore it and enter the next
# iteration of the loop, waiting for the child to end. In
# any other case, this is some other unexpected OS error,
# which we don't want to catch, so we re-raise those ones.
if e.errno != errno.EINTR:
raise
RQ在执行每一个job时都是fork一个子进程,让子进程进行处理,父进程调用waitpid等待子进程结束。代码中比较关键的点是except下面的注释,当产生SIGINT信号的时候会产生一个OSError,这个让我百思不得其解。
信号回顾
信号是进程间的一种通信方式,与其他进程间通信方式(例如管道、共享内存等)相比,信号所能传递的信息比较粗糙,只是一个整数。但正是由于传递的信息量少,信号也更便于管理和使用。
Python 中使用 signal 模块来处理信号相关的操作,定义如下:
signal.signal(signalnum, handler)
signal.getsignal(signalnum)
signalnum 为某个信号,handler 为该信号的处理函数。进程可以无视信号,可以采取默认操作,还可以自定义操作。当 handler 为 signal.SIG_IGN 时,信号被无视(ignore);当 handler 为 singal.SIG_DFL,进程采取默认操作(default);当 handler 为一个函数名时,进程采取函数中定义的操作。
- 发送信号
os.kill(pid, sid)
os.killpg(pgid, sid)
- 定时发信号
signal.alarm(2)
备注:多线程环境下使用信号,只有 main thread 可以设置 signal 的 handler,也只有它能接收到 signal。
- 信号处理实践
import signal
import time
def register_signal_handlers():
i=0
def sigint_handler(signum, frame):
nonlocal i
if i>=1:
exit()
i+=1
print("CTRL C")
print(frame.f_code)
j=0
def alarm_handler(signum,frame):
nonlocal j
if j>=2:
return
else:
print("时钟响了")
signal.signal(signal.SIGINT,sigint_handler)
signal.signal(signal.SIGALRM,alarm_handler)
def main():
register_signal_handlers()
signal.alarm(2)
func=signal.getsignal(signal.SIGINT)
while True:
time.sleep(2)
if __name__=="__main__":
main()
信号扩展
Python 信号处理程序不会在低级( C )信号处理程序中执行。相反,低级信号处理程序设置一个标志,告诉 virtual machine 稍后执行相应的 Python 信号处理程序(例如在下一个 bytecode 指令)。Python 信号处理程序总是会在主 Python 主解释器的主线程中执行,即使信号是在另一个线程中接收的。 这意味着信号不能被用作线程间通信的手段。
EINTR
-
慢调用与EINTR:如果进程在一个慢系统调用(slow system call)中阻塞时,当捕获到某个信号且相应信号处理函数返回时,这个系统调用被中断,调用返回错误,设置errno为EINTR(相应的错误描述为Interrupted system call)。永远阻塞的系统调用是指调用永远无法返回,多数网络支持函数都属于这一类。如:若没有客户连接到服务器上,那么服务器的accept调用就会一直阻塞。
-
处理方式:人为重启被中断的系统调用 或 忽略信号。
-
python处理EINTR:exception InterruptedError当系统调用被输入信号中断时将被引发。 对应于 errno EINTR。在 Python3.5更改: 当系统调用被某个信号中断时,Python 现在会重试系统调用,除非该信号的处理程序引发了其它异常 (原理参见 PEP 475) 而不是引InterruptedError。
-
pep475:标准库中提供的系统调用包装器在使用EINTR失败时应该自动重试,以减轻应用程序代码这样做的负担。如果信号处理程序成功返回,Python包装器将自动重试系统调用,python3.5以后的写法就简洁多了。
## python2写法 while True: try: data = file.read(size) break except InterruptedError: continue ## python3.5写法 while True: data = file.read(size)
相关链接
-
Signal:juejin.cn/post/684490…
-
Retry system calls failing with EINTR:www.python.org/dev/peps/pe…