我有一个忽略multiprocessing.Process
的{{1}}子类:
SIGINT
我不希望在按下 CTRL + C 时终止此过程,所以我试图通过发送{{{}来尝试在我的单元测试中模拟此终端事件1}}发信号通知此进程ID:
# inside the run method
signal.signal(signal.SIGINT, signal.SIG_IGN)
但即使没有忽略这个信号,这个过程也没有终止,所以这个测试没用,我从其他问题中发现了 CTRL + C 事件终端将SIGINT
发送到进程组ID,但我不能这样做,因为它也会终止unittest进程。
那么为什么当该进程从os.kill(PID, signal.SIGINT)
收到SIGINT
时,该进程不会终止?我应该以另一种方式这样做吗?
答案 0 :(得分:3)
子进程应在收到SIGINT时终止,除非它忽略该信号或安装了自己的处理程序。如果您没有明确忽略子节点中的SIGINT,则可能在父节点中忽略SIGINT,因此在子节点中忽略SIGINT,因为信号处置是继承的。
但是,我无法复制您的问题,事实上,我发现了相反的问题:子进程终止,无论其信号处理如何。
如果信号发送得太快,在子进程忽略SIGINT(在其run()
方法中)之前,它将被终止。以下是一些演示此问题的代码:
import os, time, signal
from multiprocessing import Process
class P(Process):
def run(self):
signal.signal(signal.SIGINT, signal.SIG_IGN)
return super(P, self).run()
def f():
print 'Child sleeping...'
time.sleep(10)
print 'Child done'
p = P(target=f)
p.start()
print 'Child started with PID', p.pid
print 'Killing child'
os.kill(p.pid, signal.SIGINT)
print 'Joining child'
p.join()
<强>输出强>
Child started with PID 1515 Killing child Joining child Traceback (most recent call last): File "p1.py", line 15, in p.start() File "/usr/lib64/python2.7/multiprocessing/process.py", line 130, in start self._popen = Popen(self) File "/usr/lib64/python2.7/multiprocessing/forking.py", line 126, in __init__ code = process_obj._bootstrap() File "/usr/lib64/python2.7/multiprocessing/process.py", line 242, in _bootstrap from . import util KeyboardInterrupt
在向孩子发送SIGINT信号之前在父母中添加time.sleep(0.1)
的小延迟将解决问题。这将为孩子提供足够的时间来执行忽略SIGINT的run()
方法。现在,孩子将忽略该信号:
Child started with PID 1589 Killing child Child sleeping... Joining child Child done
不需要延迟或自定义run()
方法的替代方法是将父级设置为忽略SIGINT,启动子级,然后恢复父级的原始SIGINT处理程序。因为信号处理是继承的,所以孩子从开始的那一刻起就会忽略SIGINT:
import os, time, signal
from multiprocessing import Process
def f():
print 'Child sleeping...'
time.sleep(10)
print 'Child done'
p = Process(target=f)
old_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN)
p.start()
signal.signal(signal.SIGINT, old_sigint) # restore parent's handler
print 'Child started with PID', p.pid
print 'Killing child'
os.kill(p.pid, signal.SIGINT)
print 'Joining child'
p.join()
<强>输出强>
Child started with PID 1660 Killing child Joining child Child sleeping... Child done
答案 1 :(得分:2)
该问题的简化版本是:
import os, time, signal
childpid = os.fork()
if childpid == 0:
# in the child
time.sleep(5) # will be interrupted by KeyboardInterrupt
print "stop child"
else:
# in the parent
#time.sleep(1)
os.kill(childpid, signal.SIGINT)
如果父节点在发送信号之前执行sleep(1)
,则一切都按预期工作:子节点(并且只有子节点)收到Python KeyboardInterrupt异常,该异常会中断sleep(5)
。但是,如果我们在上面的示例中注释sleep(1)
,kill()
似乎完全被忽略:子项运行,睡眠5秒,最后打印"stop child"
。因此,您的测试套件可以采用简单的解决方法:只需添加一个小sleep()
。
据我所知,出现以下(不好)原因:查看CPython源代码,在系统调用fork()之后,子进程显式清除挂起信号列表。但是下面的情况似乎经常发生:父母继续略微超过孩子,并发送SIGINT信号。孩子接收它,但此时它仍然只是在系统调用fork()之后不久,在_clear_pending_signals()
之前。结果,信号丢失了。
如果您想在http://bugs.python.org上提交问题,可以将此视为CPython错误。请参阅signalmodule.c中的PyOS_AfterFork()。