Klavye python'un çoklu işleme havuzuyla kesintiye uğradı


136

KeyboardInterrupt olaylarını python'un çok işlem havuzlarıyla nasıl işleyebilirim? İşte basit bir örnek:

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

Yukarıdaki kodu çalıştırırken, KeyboardInterruptbastığımda yükselir ^C, ancak süreç o noktada asılı kalır ve harici olarak öldürmek zorundayım.

^Cİstediğim zaman basabilmek ve tüm süreçlerin zarif bir şekilde çıkmasına neden olmak istiyorum .


Psutil kullanarak sorunumu çözdüm, çözümü burada görebilirsiniz: stackoverflow.com/questions/32160054/…
Tiago Albineli Motta

Yanıtlar:


137

Bu bir Python hatası. Threading.Condition.wait () öğesinde bir koşul beklenirken, KeyboardInterrupt hiçbir zaman gönderilmez. Yeniden oluşturma:

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

KeyboardInterrupt istisnası wait () geri dönünceye kadar teslim edilmez ve asla geri gelmez, bu nedenle kesinti asla gerçekleşmez. KeyboardInterrupt bir koşul beklemesini neredeyse kesmelidir.

Bir zaman aşımı belirtilirse bunun gerçekleşmeyeceğini unutmayın; cond.wait (1) kesmeyi derhal alacaktır. Bu nedenle, bir geçici çözüm bir zaman aşımı belirtmektir. Bunu yapmak için değiştirin

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

ile

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

veya benzeri.


3
Bu hata resmi python izleyicide herhangi bir yerde mi? Onu bulmakta zorlanıyorum ama muhtemelen en iyi arama terimlerini kullanmıyorum.
Joseph Garvin

18
Bu hata [Sayı 8296] [1] olarak dosyalandı. [1]: bugs.python.org/issue8296
Andrey Vlasovskikh

1
Burada, pool.imap () öğesini aynı şekilde sabitleyen ve imap üzerinden yineleme yaparken Ctrl-C'yi mümkün kılan bir kesmek. İstisnayı yakalayın ve pool.terminate () öğesini çağırın, programınız çıkacaktır. gist.github.com/626518
Alexander Ljungberg

6
Bu pek bir şey düzeltmiyor. Bazen Control + C tuşlarına bastığımda beklenen davranışı alıyorum, bazen de değil. Neden olduğundan emin değilim, ama belki de KeyboardInterrupt rastgele işlemlerden biri tarafından alınır ve sadece üst süreç onu yakalayan biri ise doğru davranışı alır gibi görünüyor.
Ryan

6
Windows'ta Python 3.6.1 ile bu benim için çalışmıyor. Ctrl-C yaptığımda ton yığını izleri ve diğer çöpler alıyorum, yani böyle bir geçici çözüm olmadan aynı. Aslında, bu iş parçacığından denediğim çözümlerin hiçbiri işe yaramıyor ...
szx

56

Son zamanlarda bulduğumdan en iyi çözüm, SIGINT'i tamamen yok saymak ve tüm temizleme kodunu üst işlemle sınırlamak için çalışan işlemleri ayarlamaktır. Bu, hem boşta hem de meşgul çalışan işlemleri için sorunu giderir ve alt işlemlerinizde hata işleme kodu gerektirmez.

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

Açıklama ve tam örnek kodu sırasıyla http://noswap.com/blog/python-multiprocessing-keyboardinterrupt/ ve http://github.com/jreese/multiprocessing-keyboardinterrupt adresinde bulunabilir .


4
Merhaba John. Çözümünüz benim maalesef karmaşık çözümümle aynı şeyi yapmıyor. time.sleep(10)Ana süreçte arkasına saklanır . Bu uykuyu kaldıracak olsaydınız ya da işlerin tamamlandığını garanti etmek için yapmanız gereken, havuzda katılmaya çalışana kadar beklerseniz, o zaman hala ana süreç olmayan aynı problemden muzdarip olursunuz. Anket joinişlemini beklerken KeyboardInterrupt'ı almayın .
bboe

Bu kodu üretimde kullandığım yerde, time.sleep () her alt işlemin durumunu kontrol eden ve gerekirse belirli bir gecikme ile belirli işlemleri yeniden başlatan bir döngünün parçasıydı. Tüm işlemlerin tamamlanmasını bekleyecek olan join () yerine, bunları tek tek kontrol eder ve ana sürecin duyarlı kalmasını sağlar.
John Reese

2
Yani daha ziyade katılmak yerine başka bir yöntemle işlemin tamamlanması için sorgulanan daha meşgul bir bekleme (belki de kontroller arasında küçük uykular)? Bu durumda, belki de bu kodu blog yayınınıza eklemek daha iyi olacaktır, çünkü daha sonra katılmaya çalışmadan önce tüm çalışanların tamamlandığını garanti edebilirsiniz.
Mart'ta bboe

4
Bu işe yaramıyor. Sadece çocuklara sinyal gönderilir. Ebeveyn asla almaz, bu yüzden pool.terminate()asla idam edilmez. Çocukların sinyali görmezden gelmesi hiçbir şey başaramaz. @ Glenn'in cevabı problemi çözer.
Cerin

1
Bunun benim versiyonum gist.github.com/admackin/003dd646e5fadee8b8d6 ; o aramazsa .join()kesme hariç - bu sadece manuel sonucunu denetler .apply_async()kullanarak AsyncResult.ready()hazır olması durumunda biz temiz bir şekilde bitirdim, yani görmek için.
Andy MacKinlay

29

Bazı nedenlerden dolayı, yalnızca temel Exceptionsınıftan devralınan özel durumlar normal şekilde ele alınır. Geçici bir çözüm KeyboardInterruptolarak, Exceptionörnek olarak yeniden yükseltebilirsiniz :

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

Normalde aşağıdaki çıktıyı alırsınız:

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

Eğer vurursanız ^C, alacaksınız:

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

2
Görünüşe göre bu tam bir çözüm değil. Bir ederse KeyboardInterruptiken geldi edilir multiprocessingdaha sonra kendi IPC arasında veri alış verişi try..catch(besbelli) aktive edilmeyecektir.
Andrey Vlasovskikh

Sen yerini alabilecek raise KeyboardInterruptErrorbir ile return. KeyboardInterrupt alınır alınmaz alt sürecin sona erdiğinden emin olmanız yeterlidir. Dönüş değeri yok sayılıyor gibi görünüyor, mainhala KeyboardInterrupt alındı.
Bernhard

8

Genellikle bu basit yapı için çalışır Ctrl- CPool tarih:

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

signal.signal(signal.SIGINT, signal_handle)

Birkaç benzer gönderide belirtildiği gibi:

Python'da denemeden klavye kesintisini yakalayın


1
Bu, çalışan işlemlerin her birinde de yapılmalıdır ve çok işlemli kitaplık başlatılırken KeyboardInterrupt yükseltilirse yine de başarısız olabilir.
MarioVilas

7

Oylanan cevap temel meseleyi değil, benzer bir yan etkiyi de ele alıyor.

Çok işlemcili kütüphanenin yazarı Jesse Noller, multiprocessing.Pooleski bir blog yazısında kullanırken CTRL + C ile nasıl doğru bir şekilde başa çıkılacağını açıklar .

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'un da aynı sorunu yaşadığını gördüm. Bulabildiğim tek düzeltme os.setpgrp()geleceğin içinden aramak oldu
portforwardpodcast

1
Elbette, tek fark ProcessPoolExecutorbaşlatıcı işlevlerini desteklememesidir. Unix'te, forkHavuzu oluşturmadan önce ana işlemdeki sighandler'ı devre dışı bırakarak ve daha sonra yeniden etkinleştirerek stratejiden yararlanabilirsiniz . Gelen çakıl , ben susturmak SIGINTvarsayılan olarak çocuk süreçler üzerinde. Python Havuzları ile aynı şeyi yapmamalarının farkında değilim. Sonunda, kullanıcı SIGINTkendisine zarar vermek istemesi durumunda işleyiciyi yeniden ayarlayabilir .
noxdafox

Bu çözüm, Ctrl-C'nin ana işlemi de kesintiye uğratmasını engelliyor gibi görünüyor.
Paul Price

1
Python 3.5 üzerinde test ettim ve işe yarıyor, hangi Python sürümünü kullanıyorsunuz? Hangi işletim sistemi?
noxdafox

5

Can sıkıcı çok işlem yaparken istisna yapan iki sorun var gibi görünüyor. Birincisi (Glenn tarafından not edildi), anında yanıt almak için map_asyncbir zaman aşımı ile kullanmanız gerektiğidir map(yani, tüm listeyi işlemeyi bitirmeyin). İkincisi (Andrey tarafından not edilmiştir), çok işlemciliğin miras almayan istisnaları yakalamadığıdır Exception(örn SystemExit.). İşte her ikisiyle de ilgilenen çözümüm:

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
Herhangi bir performans cezası fark etmedim, ancak benim durumumda functionoldukça uzun ömürlü (yüzlerce saniye).
Paul Price

Aslında bu en azından gözlerimden ve deneyimlerimden böyle değil. Tek tek alt işlemlerde klavye istisnasını yakalar ve ana işlemde bir kez daha yakalarsanız, kullanmaya devam edebilirsiniz mapve her şey yolunda. @Linux Cli Aikaşağıda bu davranışı üreten bir çözüm sağladı. map_asyncAna iş parçacığı alt işlemlerin sonuçlarına bağlıysa kullanmak her zaman istenmez.
Doggo Kodu

4

Şimdilik en iyi çözüm multiprocessing.pool özelliğini kullanmak değil, kendi havuz işlevlerinizi yuvarlamaktır. Apply_async ile hatayı gösteren bir örnek yanı sıra havuz işlevselliğini tamamen kullanmaktan kaçınmayı gösteren bir örnek sağladım.

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


Tıkır tıkır çalışıyor. Bu temiz bir çözüm ve bir tür hack değil (/ ben düşünüyor) .btw, diğerleri tarafından önerilen .get (99999) ile hile performansı kötü acıyor.
Walter

999999 yerine 9999 kullandığım halde zaman aşımı kullanmanın herhangi bir performans cezası fark etmedim. İstisna, Exception sınıfından miras almayan bir istisna yükseltildiğinde: o zaman zaman aşımı olana kadar beklemek zorundasınız çarptı. Bunun çözümü tüm istisnaları yakalamaktır (benim çözümüme bakınız).
Paul Price

1

Ben Python'da bir acemi. Ben her yerde bu ve diğer bloglar ve youtube videoları cevap ve yanılmak için bakıyordu. Yukarıdaki yazarın kodunu yapıştırın ve Windows 7 64-bit benim python 2.7.13 kopyalamak çalıştı. Neyi başarmak istediğime yakın.

Çocuk süreçlerimi ControlC'yi görmezden gelmek ve ebeveyn sürecinin sona ermesini sağlamak için yaptım. Çocuk sürecini atlamak benim için bu sorunu önlüyor gibi görünüyor.

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

Başlangıç ​​bölümü pool.terminate()hiçbir zaman yürütülmüyor gibi görünüyor.


Ben de bunu anladım! Dürüst olmak gerekirse, bunun böyle bir sorun için en iyi çözüm olduğunu düşünüyorum. Kabul edilen çözüm map_async, özellikle sevmediğim kullanıcıyı zorlar . Benimki gibi birçok durumda, ana iş parçacığının bireysel işlemlerin bitmesini beklemesi gerekir. Var olmasının nedenlerinden biri de budur map!
Doggo Kodu

1

Aşağıdaki gibi bir Pool nesnesinin Apply_async yöntemini kullanmayı deneyebilirsiniz:

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)

Çıktı:

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

Bu yöntemin bir avantajı, kesinti öncesinde işlenen sonuçların sonuç sözlüğünde döndürülmesidir:

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

Muhteşem ve eksiksiz örnek
eMTy

-5

Garip bir şekilde KeyboardInterrupt, çocuklarda da ele almanız gerekiyor gibi görünüyor . Ben yazıldığı gibi çalışması için bu umuyordum ... değiştirmeyi deneyin slowly_squareiçin:

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

Bu beklediğiniz gibi çalışmalı.


1
Bunu denedim ve işin tamamını sonlandırmıyor. Çalışmakta olan işleri sonlandırır, ancak komut dosyası hala pool.map çağrısında kalan işleri her şey normalmiş gibi atar.
Fragsworth

bu sorun değil, ancak oluşan hatalar izini kaybedebilir. üstbilginin bir hata oluştuğunu söyleyebilmesi için hatayı bir yığın iziyle döndürmek işe yarayabilir, ancak hata oluştuğunda yine de hemen çıkmaz.
mehtunguh
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.