捕获Ctrl + C/SIGINT并在python中正常退出多进程

zen*_*poy 77 python signals multiprocessing

如何在多进程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()
Run Code Online (Sandbox Code Playgroud)

它有点工作,但我不认为这是正确的解决方案.

编辑: 这可能与重复

Max*_*kin 73

先前接受的解决方案具有竞争条件,并且不起作用mapasync功能.

处理Ctrl + C/SIGINTwith 的正确方法multiprocessing.Pool是:

  1. SIGINTPool创建流程之前忽略流程.这种方式创建了子进程继承SIGINT处理程序.
  2. 创建完成SIGINT后,在父进程中还原原始处理程序Pool.
  3. 使用map_asyncapply_async不是阻止mapapply.
  4. 等待结果超时,因为默认阻塞等待忽略所有信号.这是Python bug https://bugs.python.org/issue8296.

把它放在一起:

#!/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()
Run Code Online (Sandbox Code Playgroud)

正如@YakovShklarov所指出的那样,在忽略信号和在父进程中取消它之间存在一段时间窗口,在此期间信号可能会丢失.pthread_sigmask相反,使用临时阻止父进程中的信号传递可以防止信号丢失,但是,它在Python-2中不可用.

  • 这对我来说不适用于Windows 10上的Python 3.6.1,没有捕获KeyboardInterrupt (4认同)
  • 该解决方案不可移植,因为它仅适用于Unix。而且,如果用户设置了maxtasksperchild Pool参数,那么它将不起作用。新创建的进程将再次继承标准的SIGINT处理程序。[pebble](https://pypi.python.org/pypi/Pebble)库默认在创建新进程后立即为用户禁用SIGINT。 (3认同)

zen*_*poy 33

解决方案基于此链接此链接,它解决了问题,我不得不转移到Pool:

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()
Run Code Online (Sandbox Code Playgroud)

  • 这只适用于time.sleep.如果您尝试`get()`调用`map_async`调用的结果,则中断会延迟,直到处理完成. (7认同)
  • 当然可以。但这是错误的。从文档中:“每个工作进程启动时都会调用initializer(* initargs)。” 那是“何时”,而不是“之前”。所以:比赛条件。可能发生的情况:创建了子进程,但是在signal.signal()完成之前,已发送SIGINT!子进程因未捕获的KeyboardInterrupt而中止。这种情况很少见,但不能保证不会发生。(实际上,如果您产生大量的工人,这种情况可能并不罕见。)如果您不进行阻止,则可能发生的最坏的事情似乎只是在您的终端上发生。不过,这是不好的做法。 (2认同)

der*_*kan 14

只需处理工作进程中的KeyboardInterrupt-SystemExit异常:

def worker():
    while(True):
        try:
            msg = self.msg_queue.get()
        except (KeyboardInterrupt, SystemExit):
            print("Exiting...")
            break
Run Code Online (Sandbox Code Playgroud)