用python中断键盘';s多处理池

用python中断键盘';s多处理池,python,multiprocessing,pool,keyboardinterrupt,Python,Multiprocessing,Pool,Keyboardinterrupt,如何使用python的多处理池处理键盘中断事件?下面是一个简单的例子: from multiprocessing import Pool from time import sleep from sys import exit def slowly_square(i): sleep(1) return i*i def go(): pool = Pool(8) try: results = pool.map(slowly_square, range

如何使用python的多处理池处理键盘中断事件?下面是一个简单的例子:

from multiprocessing import Pool
from time import sleep
from sys import exit

def slowly_square(i):
    sleep(1)
    return i*i

def go():
    pool = Pool(8)
    try:
        results = pool.map(slowly_square, range(40))
    except KeyboardInterrupt:
        # **** THIS PART NEVER EXECUTES. ****
        pool.terminate()
        print "You cancelled the program!"
        sys.exit(1)
    print "\nFinally, here are the results: ", results

if __name__ == "__main__":
    go()
当运行上面的代码时,当我按下
^C
键时,
键盘中断会被触发,但该进程只是挂起,我必须从外部终止它


我希望能够随时按
^C
,并使所有进程优雅地退出。

奇怪的是,看起来您还必须处理子进程中的
键盘中断。我本以为这会像写的那样有效。。。尝试将
缓慢地\u square
更改为:

def slowly_square(i):
    try:
        sleep(1)
        return i * i
    except KeyboardInterrupt:
        print 'You EVIL bastard!'
        return 0

这应该可以像您预期的那样工作。

这是一个Python错误。在threading.condition.wait()中等待条件时,键盘中断永远不会发送。复制:

import threading
cond = threading.Condition(threading.Lock())
cond.acquire()
cond.wait(None)
print "done"
KeyboardInterrupt异常在wait()返回之前不会被传递,并且它永远不会返回,因此中断永远不会发生。键盘中断几乎肯定会中断条件等待

请注意,如果指定了超时,则不会发生这种情况;cond.wait(1)将立即接收中断。因此,解决方法是指定超时。为此,请更换

    results = pool.map(slowly_square, range(40))


或类似情况。

由于某些原因,只有从基
Exception
类继承的异常才能正常处理。作为一种解决方法,您可以将
键盘中断重新提升为
异常
实例:

from multiprocessing import Pool
import time

class KeyboardInterruptError(Exception): pass

def f(x):
    try:
        time.sleep(x)
        return x
    except KeyboardInterrupt:
        raise KeyboardInterruptError()

def main():
    p = Pool(processes=4)
    try:
        print 'starting the pool map'
        print p.map(f, range(10))
        p.close()
        print 'pool map complete'
    except KeyboardInterrupt:
        print 'got ^C while pool mapping, terminating the pool'
        p.terminate()
        print 'pool is terminated'
    except Exception, e:
        print 'got exception: %r, terminating the pool' % (e,)
        p.terminate()
        print 'pool is terminated'
    finally:
        print 'joining pool processes'
        p.join()
        print 'join complete'
    print 'the end'

if __name__ == '__main__':
    main()
通常,您将获得以下输出:

staring the pool map
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
pool map complete
joining pool processes
join complete
the end
因此,如果您点击
^C
,您将获得:

staring the pool map
got ^C while pool mapping, terminating the pool
pool is terminated
joining pool processes
join complete
the end

我发现,就目前而言,最好的解决方案是不要使用multiprocessing.pool特性,而是使用自己的池功能。我提供了一个示例来演示apply_async的错误,以及一个示例来说明如何避免完全使用池功能


根据我最近的发现,最好的解决方案是将辅助进程设置为完全忽略SIGINT,并将所有清理代码限制在父进程中。这解决了空闲和繁忙工作进程的问题,并且在子进程中不需要错误处理代码

import signal

...

def init_worker():
    signal.signal(signal.SIGINT, signal.SIG_IGN)

...

def main()
    pool = multiprocessing.Pool(size, init_worker)

    ...

    except KeyboardInterrupt:
        pool.terminate()
        pool.join()

解释和完整示例代码可分别在和中找到。

通常此简单结构适用于池上的Ctrl-C:

def signal_handle(_signal, frame):
    print "Stopping the Jobs."

signal.signal(signal.SIGINT, signal_handle)
如少数类似职位所述:


在进行多处理时,似乎有两个问题会导致异常。第一个(Glenn指出)是您需要使用带超时的
map\u async
,而不是
map
,以获得即时响应(即,不要完成整个列表的处理)。第二个问题(Andrey指出)是多处理不会捕获非继承自
异常
(例如,
SystemExit
)的异常。这是我的解决方案,可以解决这两个问题:

import sys
import functools
import traceback
import multiprocessing

def _poolFunctionWrapper(function, arg):
    """Run function under the pool

    Wrapper around function to catch exceptions that don't inherit from
    Exception (which aren't caught by multiprocessing, so that you end
    up hitting the timeout).
    """
    try:
        return function(arg)
    except:
        cls, exc, tb = sys.exc_info()
        if issubclass(cls, Exception):
            raise # No worries
        # Need to wrap the exception with something multiprocessing will recognise
        import traceback
        print "Unhandled exception %s (%s):\n%s" % (cls.__name__, exc, traceback.format_exc())
        raise Exception("Unhandled exception: %s (%s)" % (cls.__name__, exc))

def _runPool(pool, timeout, function, iterable):
    """Run the pool

    Wrapper around pool.map_async, to handle timeout.  This is required so as to
    trigger an immediate interrupt on the KeyboardInterrupt (Ctrl-C); see
    http://stackoverflow.com/questions/1408356/keyboard-interrupts-with-pythons-multiprocessing-pool

    Further wraps the function in _poolFunctionWrapper to catch exceptions
    that don't inherit from Exception.
    """
    return pool.map_async(functools.partial(_poolFunctionWrapper, function), iterable).get(timeout)

def myMap(function, iterable, numProcesses=1, timeout=9999):
    """Run the function on the iterable, optionally with multiprocessing"""
    if numProcesses > 1:
        pool = multiprocessing.Pool(processes=numProcesses, maxtasksperchild=1)
        mapFunc = functools.partial(_runPool, pool, timeout)
    else:
        pool = None
        mapFunc = map
    results = mapFunc(function, iterable)
    if pool is not None:
        pool.close()
        pool.join()
    return results

我是Python的新手。我到处寻找答案,偶然发现了这个以及其他一些博客和youtube视频。我试图复制粘贴作者的代码,并在Windows7 64位的Python2.7.13上复制它。这接近我想要达到的目标

我使我的子进程忽略ControlC并使父进程终止。看起来绕过子进程确实为我避免了这个问题

#!/usr/bin/python

from multiprocessing import Pool
from time import sleep
from sys import exit


def slowly_square(i):
    try:
        print "<slowly_square> Sleeping and later running a square calculation..."
        sleep(1)
        return i * i
    except KeyboardInterrupt:
        print "<child processor> Don't care if you say CtrlC"
        pass


def go():
    pool = Pool(8)

    try:
        results = pool.map(slowly_square, range(40))
    except KeyboardInterrupt:
        pool.terminate()
        pool.close()
        print "You cancelled the program!"
        exit(1)
    print "Finally, here are the results", results


if __name__ == '__main__':
    go()
#/usr/bin/python
来自多处理导入池
从时间上导入睡眠
从系统导入退出
def(u)square(i):
尝试:
打印“睡眠后运行平方计算…”
睡眠(1)
返回i*i
除键盘中断外:
打印“不在乎你是否说CtrlC”
通过
def go():
游泳池=游泳池(8)
尝试:
结果=pool.map(方格,范围(40))
除键盘中断外:
pool.terminate()
pool.close()
打印“您取消了该计划!”
出口(1)
打印“最后,这里是结果”,结果
如果uuuu name uuuuuu='\uuuuuuu main\uuuuuuu':
go()

从pool.terminate()开始的部分似乎永远不会执行。

投票的答案没有解决核心问题,但也有类似的副作用

Jesse Noller是多处理库的作者,他解释了在旧版本中使用
multiprocessing.Pool
时如何正确处理CTRL+C


您可以尝试使用池对象的apply\u async方法,如下所示:

import multiprocessing
import time
from datetime import datetime


def test_func(x):
    time.sleep(2)
    return x**2


def apply_multiprocessing(input_list, input_function):
    pool_size = 5
    pool = multiprocessing.Pool(processes=pool_size, maxtasksperchild=10)

    try:
        jobs = {}
        for value in input_list:
            jobs[value] = pool.apply_async(input_function, [value])

        results = {}
        for value, result in jobs.items():
            try:
                results[value] = result.get()
            except KeyboardInterrupt:
                print "Interrupted by user"
                pool.terminate()
                break
            except Exception as e:
                results[value] = e
        return results
    except Exception:
        raise
    finally:
        pool.close()
        pool.join()


if __name__ == "__main__":
    iterations = range(100)
    t0 = datetime.now()
    results1 = apply_multiprocessing(iterations, test_func)
    t1 = datetime.now()
    print results1
    print "Multi: {}".format(t1 - t0)

    t2 = datetime.now()
    results2 = {i: test_func(i) for i in iterations}
    t3 = datetime.now()
    print results2
    print "Non-multi: {}".format(t3 - t2)
输出:

100
Multiprocessing run time: 0:00:41.131000
100
Non-multiprocessing run time: 0:03:20.688000
此方法的一个优点是,中断前处理的结果将在结果字典中返回:

>>> apply_multiprocessing(range(100), test_func)
Interrupted by user
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

我试过这个,但它实际上并没有终止整个作业集。它会终止当前正在运行的作业,但脚本仍会在pool.map调用中分配剩余的作业,就好像一切正常一样。这是正常的,但YOU可能会丢失对所发生错误的跟踪。使用stacktrace返回错误可能会起作用,这样父进程就可以知道发生了错误,但当错误发生时,它仍然不会立即退出。这个错误是否存在于官方python跟踪程序中?我很难找到它,但我可能只是没有使用最好的搜索词。此错误已作为[问题8296][1]提交。[1] 当前位置这并不能完全解决问题。有时,当我按下Control+C时,我会得到预期的行为,其他时候则不会。我不知道为什么,但看起来可能是其中一个进程随机接收到键盘中断,只有父进程捕获了它,我才能得到正确的行为。这对我在Windows上的Python 3.6.1不起作用。当我执行Ctrl-C时,我会收到大量的堆栈跟踪和其他垃圾,也就是说,与没有此类解决方法的情况相同。事实上,我在这个线程中尝试的解决方案似乎都不起作用……Jehejj,2019年还没有解决。就像并行执行IO一样,这是一个新颖的想法:似乎这不是一个完整的解决方案。如果在
多处理
执行其自身的IPC数据交换时到达
键盘中断
,则
try..catch
将不会激活(obv
100
Multiprocessing run time: 0:00:41.131000
100
Non-multiprocessing run time: 0:03:20.688000
>>> apply_multiprocessing(range(100), test_func)
Interrupted by user
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}