कीबोर्ड अजगर के मल्टीप्रोसेसिंग पूल के साथ हस्तक्षेप करता है


136

मैं अजगर के मल्टीप्रोसेसिंग पूल के साथ KeyboardInterrupt घटनाओं को कैसे संभाल सकता हूं? ये रहा एक सरल उदाहरण:

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

ऊपर दिए गए कोड को चलाने पर, KeyboardInterruptजब मैं दबाता हूं तो उठ जाता है ^C, लेकिन प्रक्रिया बस उस बिंदु पर लटकती है और मुझे इसे बाहरी रूप से मारना पड़ता है।

मैं ^Cकिसी भी समय प्रेस करने में सक्षम होना चाहता हूं और सभी प्रक्रियाओं को इनायत से बाहर निकलने का कारण बनना चाहता हूं ।


मैंने अपनी समस्या को psutil का उपयोग करके हल किया, आप यहाँ समाधान देख सकते हैं: stackoverflow.com/questions/32160054/…
Tiago Albineli Motta

जवाबों:


137

यह एक पायथन बग है। थ्रेडिंग में स्थिति का इंतजार करते हुए। कोंडिशन.वाइट (), KeyboardInterrupt कभी नहीं भेजा जाता है। रेप्रो:

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

प्रतीक्षा तक () रिटर्न होने तक KeyboardInterrupt अपवाद नहीं दिया जाएगा, और यह कभी नहीं लौटता है, इसलिए रुकावट कभी नहीं होती है। KeyboardInterrupt को निश्चित रूप से एक शर्त प्रतीक्षा में व्यवधान डालना चाहिए।

ध्यान दें कि यदि टाइमआउट निर्दिष्ट किया गया है तो ऐसा नहीं होता है; cond.wait (1) रुकावट तुरंत प्राप्त होगा। इसलिए, टाइमआउट निर्दिष्ट करने के लिए एक वर्कअराउंड है। ऐसा करने के लिए, बदलें

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

साथ में

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

या इसी के समान।


3
क्या यह बग कहीं आधिकारिक अजगर ट्रैकर में है? मुझे इसे खोजने में परेशानी हो रही है, लेकिन मैं शायद सबसे अच्छा खोज शब्दों का उपयोग नहीं कर रहा हूं।
जोसेफ गार्विन

18
इस बग को [इश्यू 8296] [1] के रूप में दायर किया गया है। [१]: Bugs.python.org/issue8296
एंड्री वाल्स्सोविच शेख

1
यहाँ एक हैक है जो उसी तरीके से pool.imap () को ठीक करता है, जिससे imap पर पुनरावृति होने पर Ctrl-C संभव हो जाता है। अपवाद को पकड़ो और pool.terminate () को कॉल करें और आपका प्रोग्राम बाहर निकल जाएगा। gist.github.com/626518
अलेक्जेंडर

6
यह काफी चीजें ठीक नहीं करता है। जब मैं नियंत्रण + सी दबाता हूं, तो कभी-कभी मुझे अपेक्षित व्यवहार मिलता है, अन्य बार नहीं। मुझे यकीन नहीं है कि क्यों, लेकिन ऐसा लगता है कि शायद KeyboardInterrupt यादृच्छिक प्रक्रियाओं में से एक द्वारा प्राप्त किया गया है, और मुझे केवल सही व्यवहार मिलता है अगर मूल प्रक्रिया वह है जो इसे पकड़ती है।
रयान सी। थॉम्पसन

6
यह मेरे लिए विंडोज पर अजगर 3.6.1 के साथ काम नहीं करता है। जब मैं इस तरह के वर्कअराउंड के बिना Ctrl-C करता हूं, तो मुझे स्टैक ट्रैस और अन्य कचरा मिलता है। वास्तव में इस धागे से मैंने जो भी उपाय आजमाए हैं उनमें से कोई भी काम नहीं लगता ...
szx

56

मैंने हाल ही में जो कुछ पाया है, उससे सबसे अच्छा समाधान यह है कि कार्यकर्ता प्रक्रियाओं को पूरी तरह से नजरअंदाज करने के लिए सेट करें, और सभी सफाई कोड को मूल प्रक्रिया में सीमित करें। यह बेकार और व्यस्त कार्यकर्ता प्रक्रियाओं दोनों के लिए समस्या को हल करता है, और आपके बच्चे की प्रक्रियाओं में कोई त्रुटि हैंडलिंग कोड की आवश्यकता होती है।

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 पर पाया जा सकता है ।


4
हाय जॉन। आपका समाधान मेरे, हाँ दुर्भाग्य से जटिल, समाधान के रूप में एक ही बात को पूरा नहीं करता है। यह time.sleep(10)मुख्य प्रक्रिया में पीछे छिप जाता है। यदि आप उस नींद को दूर करने के लिए थे, या जब तक आप प्रतीक्षा करते हैं, जब तक कि प्रक्रिया पूल पर शामिल होने का प्रयास नहीं करती है, जो आपको नौकरियों की गारंटी देने के लिए करना है, तो आप अभी भी उसी समस्या से ग्रस्त हैं जो मुख्य प्रक्रिया है जब तक वह पोल joinऑपरेशन पर प्रतीक्षा कर रहा है, उसे KeyboardInterrupt प्राप्त नहीं होता है ।
21

इस स्थिति में, जहां मैंने इस कोड का उत्पादन में उपयोग किया था, time.sleep () एक लूप का हिस्सा था जो प्रत्येक बच्चे की प्रक्रिया की स्थिति की जांच करेगा, और यदि आवश्यक हो तो देरी पर कुछ प्रक्रियाओं को पुनरारंभ करें। शामिल होने के बजाय () जो सभी प्रक्रियाओं को पूरा करने के लिए इंतजार करेगा, यह उन पर व्यक्तिगत रूप से जांच करेगा, यह सुनिश्चित करेगा कि मास्टर प्रक्रिया उत्तरदायी रहे।
जॉन रीज़

2
तो यह अधिक व्यस्त प्रतीक्षा थी (शायद चेक के बीच छोटी नींद के साथ) जो शामिल होने के बजाय किसी अन्य विधि के माध्यम से प्रक्रिया को पूरा करने के लिए मतदान करती है? यदि ऐसा है, तो शायद इस कोड को अपने ब्लॉग पोस्ट में शामिल करना बेहतर होगा, क्योंकि आप तब यह गारंटी दे सकते हैं कि शामिल होने का प्रयास करने से पहले सभी श्रमिकों ने पूरा कर लिया है।
बॉबी

4
यह काम नहीं करता है। केवल बच्चों को सिग्नल भेजा जाता है। माता-पिता इसे कभी प्राप्त नहीं करते हैं, इसलिए pool.terminate()कभी भी निष्पादित नहीं होते हैं। बच्चों के सिग्नल को नजरअंदाज करने से कुछ नहीं होता। @ ग्लेन का जवाब समस्या हल करता है।
सेरिन

1
इसका मेरा संस्करण gist.github.com/admackin/003dd646e5fadee8b8b6 पर है ; यह .join()रुकावट को छोड़कर कॉल नहीं करता है - यह मैन्युअल रूप से यह देखने के लिए .apply_async()उपयोग AsyncResult.ready()करने के परिणाम की जांच करता है कि क्या यह तैयार है, जिसका अर्थ है कि हम सफाई से समाप्त हो चुके हैं।
एंडी मैकिन्ले

29

कुछ कारणों से, केवल आधार Exceptionवर्ग से विरासत में मिले अपवादों को सामान्य रूप से नियंत्रित किया जाता है। वर्कअराउंड के रूप में, आप अपने उदाहरण के KeyboardInterruptरूप में फिर से बढ़ा सकते हैं 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

2
ऐसा लगता है कि यह एक पूर्ण समाधान नहीं है। यदि कोई IPC डेटा एक्सचेंज का प्रदर्शन KeyboardInterruptकरते समय आ multiprocessingजाता है, तो try..catchसक्रिय नहीं किया जाएगा (जाहिर है)।
एंड्रे व्लास्लोविच शे

आप raise KeyboardInterruptErrorए से बदल सकते हैं return। आपको बस यह सुनिश्चित करना है कि KeyboardInterrupt प्राप्त होते ही बच्चे की प्रक्रिया समाप्त हो जाए। वापसी मान को नजरअंदाज कर दिया जाता है, फिर mainभी KeyboardInterrupt प्राप्त होता है।
बर्नहार्ड

8

आम तौर पर इस सरल संरचना के लिए काम करता है Ctrl- Cपूल पर:

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

signal.signal(signal.SIGINT, signal_handle)

जैसा कि कुछ समान पोस्टों में कहा गया था:

बिना प्रयास के अजगर में कीबोर्ड पर कब्जा करें


1
यह प्रत्येक कार्यकर्ता प्रक्रियाओं पर भी किया जाना चाहिए, और अभी भी विफल हो सकता है यदि Multiprocessing पुस्तकालय प्रारंभ करते समय KeyboardInterrupt उठाया जाता है।
मारियोविलास

7

मतदान का उत्तर मूल मुद्दे से नहीं बल्कि एक समान दुष्प्रभाव से निपटता है।

मल्टीप्रोसेसिंग लाइब्रेरी के लेखक जेसी नोलर बताते हैं कि 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()

मैंने पाया है कि ProcessPoolExecutor का भी यही मुद्दा है। केवल एक ही फिक्स जो मुझे मिल पा रहा था वह था os.setpgrp()भविष्य के अंदर से कॉल करना
portforwardpodcast

1
बेशक, एकमात्र अंतर यह है कि ProcessPoolExecutorशुरुआती कार्यों का समर्थन नहीं करता है। यूनिक्स पर, आप forkपूल बनाने से पहले मुख्य प्रक्रिया पर सिग्लैंडर को अक्षम करके और उसके बाद इसे फिर से सक्षम करके रणनीति का लाभ उठा सकते हैं । में कंकड़ , मैं मौन SIGINTडिफ़ॉल्ट रूप से बच्चे की प्रक्रिया पर। मुझे इस बात की जानकारी नहीं है कि वे पायथन पूल के साथ ऐसा नहीं करते हैं। अंत में, उपयोगकर्ता उस SIGINTहैंडलर को फिर से सेट कर सकता है, जब वह खुद को / खुद को चोट पहुंचाना चाहता है।
noxdafox

यह समाधान मुख्य प्रक्रिया के साथ-साथ Ctrl-C को बाधित होने से भी रोकता है।
पॉल प्राइस

1
मैंने अभी पायथन 3.5 पर परीक्षण किया है और यह काम करता है, आप पायथन के किस संस्करण का उपयोग कर रहे हैं? क्या ओएस?
noxdafox

5

ऐसा लगता है कि दो मुद्दे हैं जो कष्टप्रद को गुणा करते हुए अपवाद बनाते हैं। पहला (ग्लेन द्वारा नोट किया गया) यह है कि आपको तत्काल प्रतिक्रिया प्राप्त करने के लिए map_asyncएक टाइमआउट के साथ उपयोग करने की आवश्यकता है map(यानी, पूरी सूची को संसाधित न करें)। दूसरा (एंड्री द्वारा विख्यात) यह है कि मल्टीप्रोसेसिंग उन अपवादों को नहीं पकड़ता है जो 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

1
मैंने कोई प्रदर्शन जुर्माना नहीं देखा है, लेकिन मेरे मामले में functionकाफी लंबे समय से जीवित है (सैकड़ों सेकंड)।
पॉल प्राइस

यह वास्तव में अब ऐसा नहीं है, कम से कम मेरी आंखों और अनुभव से। यदि आप व्यक्तिगत बाल प्रक्रियाओं में कीबोर्ड अपवाद को पकड़ते हैं और इसे मुख्य प्रक्रिया में एक बार फिर से पकड़ते हैं, तो आप उपयोग करना जारी रख सकते हैं mapऔर सभी अच्छे हैं। @Linux Cli Aikनीचे एक समाधान प्रदान किया गया है जो इस व्यवहार को उत्पन्न करता है। map_asyncयदि मुख्य प्रक्रिया को बच्चे की प्रक्रियाओं से परिणाम पर निर्भर किया जाता है, तो उपयोग करना हमेशा वांछित नहीं होता है।
कोड डोगो

4

मैंने पाया, समय के लिए, सबसे अच्छा समाधान मल्टीप्रोसेसिंग.पूल सुविधा का उपयोग नहीं करना है, बल्कि अपनी स्वयं की पूल कार्यक्षमता को रोल करना है। मैंने apply_async के साथ त्रुटि का प्रदर्शन करने के साथ-साथ पूल कार्यक्षमता का उपयोग करने से बचने के तरीके को दिखाने के लिए एक उदाहरण प्रदान किया।

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


एक जादू की तरह काम करता है। यह एक साफ समाधान है और किसी प्रकार का हैक (/ मुझे नहीं लगता) ।btw, .get (99999) के साथ चाल के रूप में दूसरों द्वारा प्रस्तावित प्रदर्शन को बुरी तरह से चोट पहुंचाता है।
वाल्टर

मैंने टाइमआउट का उपयोग करने से कोई प्रदर्शन जुर्माना नहीं देखा है, हालांकि मैं 999999 के बजाय 9999 का उपयोग कर रहा हूं। अपवाद तब होता है जब अपवाद वर्ग से विरासत में नहीं लिया गया एक अपवाद उठाया जाता है: फिर आपको टाइमआउट तक इंतजार करना होगा मारो। इसका समाधान सभी अपवादों को पकड़ना है (मेरा समाधान देखें)।
पॉल मूल्य

1

मैं पायथन में नौसिखिया हूं। मैं इस और कुछ अन्य ब्लॉग और यूट्यूब वीडियो पर जवाब और ठोकर के लिए हर जगह देख रहा था। मैंने ऊपर लेखक के कोड को कॉपी करने की कोशिश की है और इसे मेरे अजगर 2.7.13 पर विंडोज़ 7 64- बिट में पुन: पेश किया है। यह वही है जो मैं हासिल करना चाहता हूं।

मैंने अपने बच्चे को कंट्रोलसी की अनदेखी करने और माता-पिता की प्रक्रिया को समाप्त करने के लिए बनाया। ऐसा लगता है कि बच्चे की प्रक्रिया को दरकिनार करना मेरे लिए इस समस्या से बचता है।

#!/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()

पर शुरू होने वाला हिस्सा pool.terminate()कभी भी निष्पादित नहीं होता है।


मैं अभी भी यह पता लगा! मैं ईमानदारी से सोचता हूं कि इस तरह की समस्या के लिए यह सबसे अच्छा समाधान है। map_asyncउपयोगकर्ता पर स्वीकृत समाधान बल , जो मुझे विशेष रूप से पसंद नहीं है। कई स्थितियों में, मेरी तरह, मुख्य प्रक्रिया को समाप्त करने के लिए व्यक्तिगत प्रक्रियाओं की प्रतीक्षा करने की आवश्यकता होती है। यह mapमौजूद क्यों है इसका एक कारण है!
कोड डोगो

1

आप किसी पूल ऑब्जेक्ट के apply_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}

शानदार और पूर्ण उदाहरण
eMTy

-5

अजीब तरह से यह लग रहा है कि आपको KeyboardInterruptबच्चों में भी संभालना है । मुझे उम्मीद है कि यह लिखित रूप में काम करेगा ... इसे बदलने की कोशिश slowly_squareकरें:

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

आप उम्मीद के मुताबिक काम करें।


1
मैंने यह कोशिश की, और यह वास्तव में नौकरियों के पूरे सेट को समाप्त नहीं करता है। यह वर्तमान में चल रही नौकरियों को समाप्त कर देता है, लेकिन स्क्रिप्ट अभी भी पूल में शेष नौकरियों को असाइन करती है। कॉल कॉल मानो कि सब कुछ सामान्य है।
फ्राग्सवर्थ

यह ठीक है, लेकिन यूओ होने वाली त्रुटियों का ट्रैक खो सकता है। स्टैकट्रेस के साथ त्रुटि वापस करने पर काम हो सकता है, इसलिए मूल प्रक्रिया बता सकती है कि कोई त्रुटि हुई, लेकिन त्रुटि होने पर भी यह तुरंत बाहर नहीं निकलती है।
mehtunguh
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.