it-swarm.com.de

Keyboard-Interrupts mit dem Multiprocessing-Pool von Python

Wie kann ich mit den Multiprocessing-Pools von python KeyboardInterrupt-Ereignisse behandeln? Hier ist ein einfaches Beispiel:

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 __== "__main__":
    go()

Beim Ausführen des obigen Codes wird die KeyboardInterrupt angehoben, wenn ich ^C drücke, aber der Prozess hängt einfach an diesem Punkt und ich muss ihn extern beenden.

Ich möchte in der Lage sein, jederzeit ^C zu drücken und alle Prozesse ordnungsgemäß zu beenden.

116
Fragsworth

Dies ist ein Python-Fehler. Beim Warten auf eine Bedingung in threading.Condition.wait () wird KeyboardInterrupt niemals gesendet. Repro:

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

Die KeyboardInterrupt-Ausnahmebedingung wird erst geliefert, wenn wait () zurückgegeben wird, und es wird nie zurückgegeben, sodass der Interrupt niemals auftritt. KeyboardInterrupt sollte die Wartezeit eines Zustands fast sicher unterbrechen.

Beachten Sie, dass dies nicht der Fall ist, wenn ein Timeout angegeben wird. cond.wait (1) erhält die Unterbrechung sofort. Eine Problemumgehung besteht also darin, ein Timeout anzugeben. Um dies zu tun, ersetzen Sie

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

mit

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

oder ähnliches.

127
Glenn Maynard

Was ich kürzlich herausgefunden habe, ist die beste Lösung, die Arbeitsprozesse so einzurichten, dass sie SIGINT vollständig ignorieren und den gesamten Bereinigungscode auf den übergeordneten Prozess beschränken. Dies behebt das Problem für inaktive und ausgelastete Arbeitsprozesse und erfordert keinen Fehlerbehandlungscode in Ihren untergeordneten Prozessen.

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()

Erläuterungen und vollständigen Beispielcode finden Sie unter http://noswap.com/blog/python-multiprocessing-keyboardinterrupt/ und http://github.com/jreese/multiprocessing-keyboardinterrupt .

43
John Reese

Aus bestimmten Gründen werden nur Ausnahmen, die von der Exception-Basisklasse geerbt wurden, normal behandelt. Als Problemumgehung können Sie Ihre KeyboardInterrupt als Exception-Instanz erneut erhöhen:

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 __== '__main__':
    main()

Normalerweise würden Sie folgende Ausgabe erhalten:

staring the pool map
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
pool map complete
joining pool processes
join complete
the end

Wenn Sie also ^C drücken, erhalten Sie:

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

Normalerweise funktioniert diese einfache Struktur für Ctrl-C am Pool:

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

signal.signal(signal.SIGINT, signal_handle)

Wie in einigen ähnlichen Beiträgen festgestellt wurde:

Capture-Tastaturunterbrechung in Python ohne try-except

7
igco

Es scheint, dass es zwei Probleme gibt, die Ausnahmen beim Multiprocessing ärgerlich machen. Die erste (von Glenn angemerkt) besteht darin, dass Sie map_async mit einem Timeout anstelle von map verwenden müssen, um eine sofortige Antwort zu erhalten (d. H. Die Verarbeitung der gesamten Liste nicht beenden). Die zweite (von Andrey angemerkt) ist, dass bei der Mehrfachverarbeitung keine Ausnahmen erfasst werden, die nicht von Exception erben (z. B. SystemExit). Hier ist meine Lösung, die sich mit diesen beiden befasst:

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
5
Paul Price

Die abgestimmte Antwort geht nicht das Kernproblem an, sondern einen ähnlichen Nebeneffekt.

Jesse Noller, der Autor der Multiprocessing-Bibliothek, erläutert, wie mit STRG + C bei der Verwendung von multiprocessing.Pool in einem alten blog post richtig verfahren wird.

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()
4
noxdafox

Ich habe vorläufig die beste Lösung gefunden, die Multiprocessing.pool-Funktion nicht zu verwenden, sondern stattdessen Ihre eigene Pool-Funktionalität zu rollen. Ich habe ein Beispiel zur Veranschaulichung des Fehlers mit apply_async sowie ein Beispiel zur Vermeidung der vollständigen Verwendung der Pool-Funktionalität bereitgestellt.

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

3
bboe

Ich bin ein Neuling in Python. Ich suchte überall nach Antworten und stolperte über dieses und ein paar andere Blogs und YouTube-Videos. Ich habe versucht, den Code des Autors oben zu kopieren und ihn auf meinem Python 2.7.13 in Windows 7 64-Bit zu reproduzieren. Es ist nahe an dem, was ich erreichen möchte.

Ich habe meine Kindprozesse dazu gebracht, das ControlC zu ignorieren und den übergeordneten Prozess zu beenden. Sieht aus, als würde das Umgehen des Kindprozesses dieses Problem für mich vermeiden.

#!/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 __== '__main__':
    go()

Der Teil, der mit pool.terminate() beginnt, scheint nie ausgeführt zu werden.

1
Linux Cli Aik

Sie können versuchen, die apply_async-Methode eines Pool-Objekts wie folgt zu verwenden:

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 __== "__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)

Ausgabe:

100
Multiprocessing run time: 0:00:41.131000
100
Non-multiprocessing run time: 0:03:20.688000

Ein Vorteil dieser Methode ist, dass vor der Unterbrechung verarbeitete Ergebnisse im Ergebniswörterbuch zurückgegeben werden:

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