Python İşlem Havuzu daemonic değil mi?


104

Daemonik olmayan bir python Havuzu oluşturmak mümkün olabilir mi? Bir havuzun içinde başka bir havuzu olan bir işlevi çağırabilmesini istiyorum.

Bunu istiyorum çünkü deamon süreçleri süreç yaratamaz. Özellikle şu hataya neden olur:

AssertionError: daemonic processes are not allowed to have children

Örneğin, senaryoyu düşünün function_abir havuz çalışır sahiptir function_bçalışan bir havuz vardır function_c. Bu işlev zinciri başarısız olur çünkü function_bbir arka plan programı sürecinde çalıştırılır ve arka plan programı süreçleri süreçler oluşturamaz.


AFAIK, hayır, havuzdaki tüm çalışanların daemonize edilmesi mümkün değil ve bağımlılığı enjekte etmek mümkün değil , BTW Sorunuzun ikinci bölümünü anlamıyorum ve I want a pool to be able to call a function that has another pool insidebunun işçilerin daemonize edilmesine nasıl engel olduğunu anlamıyorum.
mouad

5
Çünkü a işlevi, c işlevini çalıştıran bir havuzu olan b işlevini çalıştıran bir havuza sahipse, b'de bunun bir daemon sürecinde çalıştırılması ve daemon işlemlerinin süreçler yaratamaması gibi bir sorun vardır. AssertionError: daemonic processes are not allowed to have children
Maksimum

Yanıtlar:


126

multiprocessing.pool.PoolSınıf onun alt işlemleri oluşturur __init__daemonic bunları yapar ve bunları başlar yöntemi ve onların yeniden belirlemesi mümkün değildir daemonözniteliği Falseçalıştırılmadan (ve sonradan artık izni yok) önce. Ancak kendi alt sınıfınızı multiprocesing.pool.Pool( multiprocessing.Poolsadece bir sarmalayıcı işlevidir) oluşturabilir ve multiprocessing.Processher zaman arka plan dışı olan kendi alt sınıfınızı işçi süreçleri için kullanmak üzere değiştirebilirsiniz.

İşte bunun nasıl yapılacağına dair tam bir örnek. Önemli bölümleri iki sınıfları vardır NoDaemonProcessve MyPoolüstündeki ve çağrı pool.close()ve pool.join()sizin üzerinde MyPoolsonundaki örneği.

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import multiprocessing
# We must import this explicitly, it is not imported by the top-level
# multiprocessing module.
import multiprocessing.pool
import time

from random import randint


class NoDaemonProcess(multiprocessing.Process):
    # make 'daemon' attribute always return False
    def _get_daemon(self):
        return False
    def _set_daemon(self, value):
        pass
    daemon = property(_get_daemon, _set_daemon)

# We sub-class multiprocessing.pool.Pool instead of multiprocessing.Pool
# because the latter is only a wrapper function, not a proper class.
class MyPool(multiprocessing.pool.Pool):
    Process = NoDaemonProcess

def sleepawhile(t):
    print("Sleeping %i seconds..." % t)
    time.sleep(t)
    return t

def work(num_procs):
    print("Creating %i (daemon) workers and jobs in child." % num_procs)
    pool = multiprocessing.Pool(num_procs)

    result = pool.map(sleepawhile,
        [randint(1, 5) for x in range(num_procs)])

    # The following is not really needed, since the (daemon) workers of the
    # child's pool are killed when the child is terminated, but it's good
    # practice to cleanup after ourselves anyway.
    pool.close()
    pool.join()
    return result

def test():
    print("Creating 5 (non-daemon) workers and jobs in main process.")
    pool = MyPool(5)

    result = pool.map(work, [randint(1, 5) for x in range(5)])

    pool.close()
    pool.join()
    print(result)

if __name__ == '__main__':
    test()

1
Kodumu Linux ve Python 2.6 / 2.7 / 3.2 OS X'te Python 2.7 / 3.2 ("yazdırma" satırlarını düzelttikten sonra) ile tekrar test ettim. OS X üzerinde Linux ve Python 2.7 / 3.2 iyi çalışıyor ancak kod gerçekten takılıyor OS X (Lion) üzerinde Python 2.6. Bu, çoklu işlem modülünde düzeltilen bir hata gibi görünüyor, ancak hata izleyiciyi gerçekten kontrol etmedim.
Chris Arndt

1
Teşekkürler! Windows'ta da aramanız gerekirmultiprocessing.freeze_support()
frmdstryr 04

2
İyi iş. Bununla bellek sızıntısı olan biri varsa, havuzu düzgün bir şekilde elden çıkarmak için "havuz olarak kapatarak (Havuzum (işlemler = num_cpu)):" kullanmayı deneyin
Chris Lucian

33
MyPoolVarsayılan yerine kullanmanın dezavantajları nelerdir Pool? Başka bir deyişle, alt süreçleri başlatma esnekliği karşılığında ne tür masraflar öderim? (Maliyet Poololmasaydı , muhtemelen standart daemonik olmayan süreçleri kullanırdı).
en fazla

4
@machen Evet, maalesef bu doğru. Python 3.6'da Poolsınıf kapsamlı bir şekilde yeniden düzenlenmiştir, bu nedenle Processartık basit bir öznitelik değil, bir bağlamdan aldığı süreç örneğini döndüren bir yöntemdir . Bir NoDaemonPoolörnek döndürmek için bu yöntemin üzerine yazmayı denedim , ancak bu AssertionError: daemonic processes are not allowed to have children, Havuz kullanıldığında istisna ile sonuçlanıyor .
Chris Arndt

31

Python 3.7'de daemonik olmayan bir havuz kullanma zorunluluğum vardı ve kabul edilen cevapta yayınlanan kodu uyarladım. Aşağıda daemonik olmayan havuzu oluşturan snippet var:

import multiprocessing.pool

class NoDaemonProcess(multiprocessing.Process):
    @property
    def daemon(self):
        return False

    @daemon.setter
    def daemon(self, value):
        pass


class NoDaemonContext(type(multiprocessing.get_context())):
    Process = NoDaemonProcess

# We sub-class multiprocessing.pool.Pool instead of multiprocessing.Pool
# because the latter is only a wrapper function, not a proper class.
class NestablePool(multiprocessing.pool.Pool):
    def __init__(self, *args, **kwargs):
        kwargs['context'] = NoDaemonContext()
        super(NestablePool, self).__init__(*args, **kwargs)

Şu anki uygulaması multiprocessing, bağlamlara dayalı olarak kapsamlı bir şekilde yeniden düzenlendiğinden, as niteliğimize NoDaemonContextsahip bir sınıf sağlamamız gerekiyor NoDaemonProcess. NestablePoolo zaman varsayılan bağlam yerine bu bağlamı kullanacaktır.

Bununla birlikte, bu yaklaşımla ilgili en az iki uyarı olduğu konusunda uyarmalıyım:

  1. Yine de multiprocessingpaketin uygulama ayrıntılarına bağlıdır ve bu nedenle herhangi bir zamanda bozulabilir.
  2. multiprocessingDaemonik olmayan süreçlerin kullanılmasının bu kadar zor olmasının geçerli nedenleri vardır ve bunların çoğu burada açıklanmıştır . Bence en zorlayıcı:

Alt süreci kullanarak çocuk iş parçacıklarının kendi çocuklarından doğmasına izin vermek ise, alt süreç tamamlanmadan ve geri dönmeden ana veya alt iş parçacığı sona ererse küçük bir zombi 'torun' ordusu oluşturma riskini taşır.


1
İhtar ilgili olarak: My kullanım durumu görevleri parallelising, ancak büyük-çocuk olduğunu ebeveynlerine bilgi döndürmek için dönüş dönüş bilgilerinde onların ebeveynleri sonra bazı gerekli yerel işleme yapıyor. Sonuç olarak, her seviye / dal, tüm yaprakları için açık bir bekleme süresine sahiptir. Açıkça ortaya çıkan işlemlerin bitmesini beklemeniz gerekiyorsa uyarı hala geçerli mi?
A_A

Multiprocessing.pool yerine bunun nasıl kullanılacağını eklemeye zahmet eder misiniz?
Radyo Kontrollü

"Artık multiprocessing.Pool ve NestablePool'u birbirinin yerine kullanabilirsiniz".
Radyo Kontrollü

24

Çoklu işlem modülü süreçlerle havuzları kullanmak için güzel bir arayüze sahip veya iplikler. Mevcut kullanım durumunuza bağlı olarak, multiprocessing.pool.ThreadPooldış Havuzunuz için kullanmayı düşünebilirsiniz , bu da işlemlerin aksine iş parçacıklarıyla (işlemlerin içeriden oluşturulmasına izin verir) sonuçlanır .

GIL ile sınırlı olabilir, ancak benim özel durumumda (her ikisini de test ettim) , buradaPool oluşturulan süreçlerin dıştan başlama süresi , çözümden çok daha ağır basıyor .ThreadPool


Bu takas gerçekten çok kolay Processesiçin Threads. Bir ThreadPoolçözümün nasıl kullanılacağı hakkında daha fazlasını buradan veya buradan okuyun .


Teşekkürler - bu bana çok yardımcı oldu - burada iş parçacığının büyük kullanımı (gerçekten iyi performans gösteren süreçleri ortaya
çıkarmak için

1
Muhtemelen kendi durumları için geçerli olan pratik bir çözüm arayan insanlar için bu çözümdür.
abanana

6

Özel standart Pool yerine bazı Python sürümlerinde hatayı yükseltebilirsiniz: AssertionError: group argument must be None for now.

Burada yardımcı olabilecek bir çözüm buldum:

class NoDaemonProcess(multiprocessing.Process):
    # make 'daemon' attribute always return False
    @property
    def daemon(self):
        return False

    @daemon.setter
    def daemon(self, val):
        pass


class NoDaemonProcessPool(multiprocessing.pool.Pool):

    def Process(self, *args, **kwds):
        proc = super(NoDaemonProcessPool, self).Process(*args, **kwds)
        proc.__class__ = NoDaemonProcess

        return proc

4

concurrent.futures.ProcessPoolExecutorbu sınırlamaya sahip değil. Hiç sorun olmadan iç içe geçmiş bir işlem havuzuna sahip olabilir:

from concurrent.futures import ProcessPoolExecutor as Pool
from itertools import repeat
from multiprocessing import current_process
import time

def pid():
    return current_process().pid

def _square(i):  # Runs in inner_pool
    square = i ** 2
    time.sleep(i / 10)
    print(f'{pid()=} {i=} {square=}')
    return square

def _sum_squares(i, j):  # Runs in outer_pool
    with Pool(max_workers=2) as inner_pool:
        squares = inner_pool.map(_square, (i, j))
    sum_squares = sum(squares)
    time.sleep(sum_squares ** .5)
    print(f'{pid()=}, {i=}, {j=} {sum_squares=}')
    return sum_squares

def main():
    with Pool(max_workers=3) as outer_pool:
        for sum_squares in outer_pool.map(_sum_squares, range(5), repeat(3)):
            print(f'{pid()=} {sum_squares=}')

if __name__ == "__main__":
    main()

Yukarıdaki gösteri kodu Python 3.8 ile test edildi.

ProcessPoolExecutorBununla birlikte, bir sınırlaması, sahip olmamasıdır maxtasksperchild. Buna ihtiyacınız varsa, bunun yerine Massimiliano'nun cevabını düşünün .

Kredi: jfs tarafından yanıt


1
Minimum değişiklik gerektirdiğinden, bu artık açıkça en iyi çözümdür.
DreamFlasher

1
Mükemmel çalışıyor! ... bir çocuğun multiprocessing.Pooliçinde bir yan not olarak - içinde a ProcessPoolExecutor.Poolda mümkündür!
raphael

4

Karşılaştığım sorun, küreselleri modüller arasında içe aktarmaya çalışmaktı ve ProcessPool () satırının birden çok kez değerlendirilmesine neden oluyordu.

globals.py

from processing             import Manager, Lock
from pathos.multiprocessing import ProcessPool
from pathos.threading       import ThreadPool

class SingletonMeta(type):
    def __new__(cls, name, bases, dict):
        dict['__deepcopy__'] = dict['__copy__'] = lambda self, *args: self
        return super(SingletonMeta, cls).__new__(cls, name, bases, dict)

    def __init__(cls, name, bases, dict):
        super(SingletonMeta, cls).__init__(name, bases, dict)
        cls.instance = None

    def __call__(cls,*args,**kw):
        if cls.instance is None:
            cls.instance = super(SingletonMeta, cls).__call__(*args, **kw)
        return cls.instance

    def __deepcopy__(self, item):
        return item.__class__.instance

class Globals(object):
    __metaclass__ = SingletonMeta
    """     
    This class is a workaround to the bug: AssertionError: daemonic processes are not allowed to have children
     
    The root cause is that importing this file from different modules causes this file to be reevalutated each time, 
    thus ProcessPool() gets reexecuted inside that child thread, thus causing the daemonic processes bug    
    """
    def __init__(self):
        print "%s::__init__()" % (self.__class__.__name__)
        self.shared_manager      = Manager()
        self.shared_process_pool = ProcessPool()
        self.shared_thread_pool  = ThreadPool()
        self.shared_lock         = Lock()        # BUG: Windows: global name 'lock' is not defined | doesn't affect cygwin

Ardından kodunuzda başka bir yerden güvenle içe aktarın

from globals import Globals
Globals().shared_manager      
Globals().shared_process_pool
Globals().shared_thread_pool  
Globals().shared_lock         

pathos.multiprocessingBurada daha genişletilmiş bir sarmalayıcı sınıfı yazdım :

Bir yan not olarak, eğer kullanım durumunuz bir performans optimizasyonu olarak sadece eşzamansız çoklu işlem haritası gerektiriyorsa, joblib arka planda tüm süreç havuzlarınızı yönetecek ve bu çok basit sözdizimine izin verecektir:

squares = Parallel(-1)( delayed(lambda num: num**2)(x) for x in range(100) )

3

Bu sorunu , arka planda çalışan süreçlerin çocukları doğurmasına izin veren bilardo denilen celeryçatal (çoklu işlem havuz uzantıları) kullanarak ele alan insanlar gördüm . Çözüm yolu, modülü basitçe şu şekilde değiştirmektir :multiprocessingmultiprocessing

import billiard as multiprocessing

0

Bu, hatanın görünüşte yanlış pozitif olduğu durumlar için bir geçici çözüm sunar. James'in de belirttiği gibi , bu, arka planda çalışan bir süreçten kasıtsız bir içe aktarmaya neden olabilir.

Örneğin, aşağıdaki basit koda sahipseniz WORKER_POOL, yanlışlıkla bir çalışandan içe aktarılabilir ve bu da hataya neden olabilir.

import multiprocessing

WORKER_POOL = multiprocessing.Pool()

Geçici çözüm için basit ama güvenilir bir yaklaşım şudur:

import multiprocessing
import multiprocessing.pool


class MyClass:

    @property
    def worker_pool(self) -> multiprocessing.pool.Pool:
        # Ref: https://stackoverflow.com/a/63984747/
        try:
            return self._worker_pool  # type: ignore
        except AttributeError:
            # pylint: disable=protected-access
            self.__class__._worker_pool = multiprocessing.Pool()  # type: ignore
            return self.__class__._worker_pool  # type: ignore
            # pylint: enable=protected-access

Yukarıdaki geçici çözümde, MyClass.worker_poolhatasız kullanılabilir. Bu yaklaşımın geliştirilebileceğini düşünüyorsanız bana bildirin.

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.