Catch Ctrl+;C/SIGINT并在python中优雅地退出多进程

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)

如何在多进程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)
        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
  • 等待结果并超时,因为默认阻塞等待忽略所有信号。这是Python bug

  • 综合起来:

    #!/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()
    ,而无需超时: