Catch Ctrl+;C/SIGINT并在python中优雅地退出多进程
如何在多进程python程序中捕获Ctrl+C并优雅地退出所有进程,我需要在unix和windows上同时工作的解决方案。我尝试了以下方法:Catch Ctrl+;C/SIGINT并在python中优雅地退出多进程,python,multiprocessing,signals,Python,Multiprocessing,Signals,如何在多进程python程序中捕获Ctrl+C并优雅地退出所有进程,我需要在unix和windows上同时工作的解决方案。我尝试了以下方法: import multiprocessing import time import signal import sys jobs = [] def worker(): signal.signal(signal.SIGINT, signal_handler) while(True): time.sleep(1.1234)
import multiprocessing
import time
import signal
import sys
jobs = []
def worker():
signal.signal(signal.SIGINT, signal_handler)
while(True):
time.sleep(1.1234)
print "Working..."
def signal_handler(signal, frame):
print 'You pressed Ctrl+C!'
# for p in jobs:
# p.terminate()
sys.exit(0)
if __name__ == "__main__":
for i in range(50):
p = multiprocessing.Process(target=worker)
jobs.append(p)
p.start()
这是可行的,但我认为这不是正确的解决方案。该解决方案基于,解决了问题,但我不得不转移到池
:
import multiprocessing
import time
import signal
import sys
def init_worker():
signal.signal(signal.SIGINT, signal.SIG_IGN)
def worker():
while(True):
time.sleep(1.1234)
print "Working..."
if __name__ == "__main__":
pool = multiprocessing.Pool(50, init_worker)
try:
for i in range(50):
pool.apply_async(worker)
time.sleep(10)
pool.close()
pool.join()
except KeyboardInterrupt:
print "Caught KeyboardInterrupt, terminating workers"
pool.terminate()
pool.join()
只需在工作进程中处理键盘中断系统退出异常:
def worker():
while(True):
try:
msg = self.msg_queue.get()
except (KeyboardInterrupt, SystemExit):
print("Exiting...")
break
具有竞态条件,它不适用于map
和async
函数
使用多处理池处理Ctrl+C/
SIGINT
的正确方法是:
SIGINT
。这样创建的子进程继承SIGINT
处理程序池后,还原父进程中的原始SIGINT
处理程序
map\u async
和apply\u async
而不是阻塞map
和apply
综合起来:
#!/bin/env python
from __future__ import print_function
import multiprocessing
import os
import signal
import time
def run_worker(delay):
print("In a worker process", os.getpid())
time.sleep(delay)
def main():
print("Initializng 2 workers")
original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
pool = multiprocessing.Pool(2)
signal.signal(signal.SIGINT, original_sigint_handler)
try:
print("Starting 2 jobs of 5 seconds each")
res = pool.map_async(run_worker, [5, 5])
print("Waiting for results")
res.get(60) # Without the timeout this blocking call ignores all signals.
except KeyboardInterrupt:
print("Caught KeyboardInterrupt, terminating workers")
pool.terminate()
else:
print("Normal termination")
pool.close()
pool.join()
if __name__ == "__main__":
main()
正如@YakovShklarov所指出的,在忽略信号和在父进程中忽略信号之间有一个时间窗口,在此期间,信号可能会丢失。使用
pthread\u sigmask
而不是临时阻止父进程中的信号传递可以防止信号丢失,但是,Python-2中没有该信号。这有点太晚了:在子进程中的fork()
return和signal()
调用之间有一个竞争条件窗口。分叉之前必须阻止信号。@MaximYegorushkin-信号在init\u worker
中被阻止,它在apply\u async
之前被调用-这就是您所说的吗?这只会因为时间而起作用。sleep。如果尝试get()
而不是map\u async
调用的结果,则中断会延迟到处理完成。这是错误的答案。正确答案:当然可以。但这是错误的。从文档中可以看出:“每个工作进程在启动时将调用初始值设定项(*initargs)。”这是“何时”,而不是“之前”。所以:比赛条件。下面是可能发生的情况:创建子流程,但在signal.signal()完成之前,会发送SIGINT!子进程通过未捕获的键盘中断中止。这种情况很少见,但不能保证不会发生。(事实上,如果你正在培养成吨的工人,这可能并不罕见。)如果你不阻止,最糟糕的事情可能发生在你的终端上。不过,这是一种不好的做法。对于让Python引发SystemExit的信号来说,在Python3.6上也是如此。我想知道,这包括什么信号?我猜是SIGKILL和SIGTERM…?你可以很容易地检查哪些信号包括,答案是:我认为没有。系统退出仅由sys.exit根据单据提出。只需执行try:time.sleep(60),但BaseException除外,如e:print(e)
,您将看到是否捕获了特定的信号(仅ime SIGINT)。这也是手册页所说的,可能只是SIGINT。我相信SIGKILL是不可跟踪的,而SIGTERM则是另外一回事。似乎您必须使用map\u async,而不是map,有人能提到单个处理的区别吗?(调用.get on the map_async result似乎也没有必要)这对我在Windows10上的Python 3.6.1不起作用,键盘中断不是caught@Boop我不确定,需要对此进行调查。此解决方案不可移植,因为它只能在Unix上工作。此外,如果用户设置maxtasksperchild
Pool参数,则此操作将不起作用。新创建的进程将再次继承标准的SIGINT
处理程序。默认情况下,创建新进程后,库会立即为用户禁用SIGINT
。请注意,在Python 3.3中,阻塞调用问题已得到解决,您可以使用map()
、apply()
和get()
,而无需超时: