2009-09-10 43 views
102

如何處理python的多處理池中的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(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() 

當運行上面的代碼,當我按下^CKeyboardInterrupt被提出,但這一過程只是掛起,在這一點上,我不得不從外部殺死它。

我希望能夠隨時按^C,並使所有進程正常退出。

+0

我解決了使用psutil我的問題,你可以看到這裏的解決方案:https://stackoverflow.com/questions/32160054/keyboard-interrupts-with-pythons-multiprocessing-pool-and-map-function/ 45259908#45259908 – 2017-07-22 22:50:05

回答

113

這是一個Python錯誤。在等待threading.Condition.wait()中的條件時,KeyboardInterrupt永遠不會被髮送。攝製:

import threading 
cond = threading.Condition(threading.Lock()) 
cond.acquire() 
cond.wait(None) 
print "done" 

的KeyboardInterrupt異常之前,不會等待交付()返回時,它永遠不會返回,因此中斷不會發生。 KeyboardInterrupt幾乎肯定會中斷一個等待狀態。

請注意,如果指定超時,則不會發生這種情況; cond.wait(1)會立即收到中斷。所以,一個解決方法是指定一個超時。要做到這一點,與

results = pool.map_async(slowly_square, range(40)).get(9999999) 

或類似的替代

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

+3

這是官方python tracker中的這個bug嗎?我很難找到它,但我可能只是沒有使用最好的搜索條件。 – 2010-03-10 21:16:12

+14

此錯誤已作爲[問題8296] [1]提交。 [1]:http://bugs.python.org/issue8296 – 2010-04-08 04:31:25

+1

這裏有一個修改pool.imap()方法相同的方法,使得在迭代imap時可以使用Ctrl-C。捕獲異常並調用pool.terminate(),您的程序將退出。 http://gist.github.com/626518 – 2010-10-14 16:40:34

-4

奇怪的是,它看起來像你還必須在兒童中處理KeyboardInterrupt。我本來期望這個工作,因爲寫的......請嘗試更改slowly_square到:

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

如你預期這應該工作。

+1

我試過了,並沒有真正終止整套工作。它會終止當前正在運行的作業,但腳本仍然會將pool.map調用中的其餘作業分配爲一切正常。 – Fragsworth 2009-09-11 00:39:07

+0

這是好的,但yuo可能會失去所發生錯誤的蹤跡。用堆棧跟蹤返回錯誤可能會起作用,所以父進程可以知道發生了錯誤,但在發生錯誤時它仍不會立即退出。 – mehtunguh 2013-10-22 13:58:50

23

由於某些原因,只有繼承自基類Exception類的例外才能正常處理。作爲一種變通方法,你可能會重新提高你KeyboardInterruptException實例:

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 
+2

看來這不是一個完整的解決方案。如果在'multiprocessing'執行自己的IPC數據交換時到達'KeyboardInterrupt',那麼'try..catch'將不會被激活(顯然)。 – 2010-04-03 02:33:55

+1

這對我很好。 – mudda 2015-09-14 14:51:55

+0

您可以用'return'替換'raise KeyboardInterruptError'。只要收到KeyboardInterrupt,您就必須確保子進程結束。返回值似乎被忽略,在'main'中仍然收到KeyboardInterrupt。 – TheEspinosa 2016-03-14 10:22:21

3

我發現,目前最好的解決方案是不使用multiprocessing.pool功能,而是使用自己的池功能。我提供了一個演示apply_async錯誤的示例以及一個演示如何完全避免使用池功能的示例。

http://www.bryceboe.com/2010/08/26/python-multiprocessing-and-keyboardinterrupt/

+0

工程就像一個魅力。這是一個乾淨的解決方案,而不是某種破解(/我認爲).btw,其他人提出的.get(99999)技巧會嚴重影響性能。 – Walter 2013-06-21 11:53:43

+0

我沒有注意到使用超時會導致任何性能損失,儘管我一直使用9999而不是999999.當異常沒有從Exception類繼承時,異常是異常:那麼您必須等到超時被擊中。解決方案是捕獲所有異常(請參閱我的解決方案)。 – 2014-05-15 15:26:03

40

從我最近發現,最好的解決辦法是成立工作進程完全忽略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() 

說明和完整的示例代碼可在分別http://noswap.com/blog/python-multiprocessing-keyboardinterrupt/http://github.com/jreese/multiprocessing-keyboardinterrupt找到。

+3

嗨,約翰。您的解決方案與我的解決方案無法完成相同的功能,不幸的是,解決方案很複雜它隱藏在主進程中的'time.sleep(10)'後面。如果你要消除這種睡眠,或者如果你等到該過程試圖加入池中,爲了保證作業完成,你必須這樣做,那麼你仍然遭受同樣的問題,這是主要過程在它等待輪詢加入操作時,不會收到KeyboardInterrupt。 – bboe 2012-02-14 21:06:11

+0

在生產中我使用此代碼的情況下,time.sleep()是循環的一部分,它將檢查每個子進程的狀態,然後根據需要重新啓動某些進程。而不是join()等待所有進程完成,它將單獨檢查它們,確保主進程保持響應。 – 2012-03-29 16:08:23

+2

因此,它更忙於等待(可能在檢查之間有小睡),通過另一種方法輪詢流程完成而不是加入?如果是這樣的話,也許最好在你的博客文章中包含這些代碼,因爲你可以保證所有的工作人員在嘗試加入之前完成了。 – bboe 2012-03-29 19:16:50

6

通常這種簡單的結構,適用於按Ctrl - ç在游泳池:

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

signal.signal(signal.SIGINT, signal_handle) 

正如幾個類似的帖子指出:

Capture keyboardinterrupt in Python without try-except

+0

這也必須在每個工作進程上完成,並且如果在多處理庫初始化時引發KeyboardInterrupt,則可能仍會失敗。 – MarioVilas 2013-06-15 16:26:51

5

似乎有兩個問題在多處理令人討厭時會造成異常。第一個(由Glenn注意到)是你需要使用map_async而不是map以獲得即時響應(即,不完成整個列表的處理)。第二個(由Andrey指出)是多處理不會捕獲不會從Exception(例如,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 
+0

這個解決方案的性能損失是多少? – Pierpaolo 2014-12-02 06:19:27

+1

我沒有注意到任何性能損失,但在我的情況下'功能'是相當長壽(數百秒)。 – 2014-12-02 16:24:46

0

我是Python的新手。我到處尋找答案,並偶然發現這個以及其他一些博客和YouTube視頻。我試圖複製粘貼上面的作者的代碼,並在Windows 7 64位我的Python 2.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: 
     # *** THIS PART NEVER EXECUTES. *** :(
     pool.terminate() 
     pool.close() 
     print "You cancelled the program!" 
     exit(1) 
    print "Finally, here are the results", results 


if __name__ == '__main__': 
    go() 
2

投票答案沒有解決核心問題,但類似的副作用。

多處理庫的作者Jesse Noller解釋瞭如何在舊的blog post中使用multiprocessing.Pool時正確處理CTRL + C.

import signal 
from multiprocessing import Pool 


def initializer(): 
    """Ignore CTRL+C in the worker process.""" 
    signal.signal(signal.SIGINT, signal.SIG_IGN) 


pool = Pool(initializer=initializer) 

try: 
    pool.map(perform_download, dowloads) 
except KeyboardInterrupt: 
    pool.terminate() 
    pool.join() 
+0

我發現ProcessPoolExecutor也有同樣的問題。我能找到的唯一解決方法是從未來調用os.setpgrp()' – portforwardpodcast 2017-09-12 04:38:02

+1

當然,唯一的區別是'ProcessPoolExecutor'不支持初始化函數。在Unix上,你可以利用'fork'策略,在創建池之前禁用主進程的sighandler並在之後重新啓用它。在[pebble](https://github.com/noxdafox/pebble)中,默認情況下我在子進程上聲明瞭'SIGINT'。我不知道他們在Python池中不這樣做的原因。最後,用戶可以重新設置'SIGINT'處理程序,以防他/她想傷害自己。 – noxdafox 2017-09-12 06:34:08

+0

此解決方案似乎也可以防止Ctrl-C中斷主進程。 – 2018-02-14 19:07:00