Development Tip

Python 프로세스 풀 비 데모닉?

yourdevel 2020. 10. 16. 08:07
반응형

Python 프로세스 풀 비 데모닉?


데모닉이 아닌 파이썬 풀을 만들 수 있습니까? 풀이 내부에 다른 풀이있는 함수를 호출 할 수 있기를 원합니다.

데몬 프로세스는 프로세스를 만들 수 없기 때문에 이것을 원합니다. 특히 다음과 같은 오류가 발생합니다.

AssertionError: daemonic processes are not allowed to have children

예를 들어, function_a를 실행하는 풀이있는 실행 function_b중인 풀이있는 시나리오를 고려하십시오 function_c. 이 함수 체인은 function_b데몬 프로세스에서 실행되고 있기 때문에 실패 하고 데몬 프로세스는 프로세스를 만들 수 없습니다.


multiprocessing.pool.Pool클래스는에서 작업자 프로세스 생성 __init__악마의를하게하고이를 시작 방법을, 그리고 자신의-다시 설정 할 수 없습니다 daemon에 속성을 False가 시작된다 (그리고 나중에 그것이 더 이상 허용하지 않는다) 전에. 그러나 multiprocesing.pool.Pool( multiprocessing.Pool은 래퍼 함수일뿐입니다)의 고유 한 multiprocessing.Process하위 클래스를 만들고 작업자 프로세스에 사용할 수 있도록 항상 비 데모닉 인 자체 하위 클래스로 대체 할 수 있습니다.

다음은이를 수행하는 방법에 대한 전체 예입니다. 중요한 부분은 두 개의 클래스입니다 NoDaemonProcessMyPool상단 및 호출 pool.close()하고 pool.join()MyPool끝에 인스턴스입니다.

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

멀티 프로세싱 모듈은 프로세스와 풀을 사용하기 좋은 인터페이스를 가지고 또는 스레드. 현재 사용 사례 multiprocessing.pool.ThreadPool에 따라 외부 풀에 사용 하는 것을 고려할 수 있습니다. 그러면 프로세스가 아닌 스레드 (내부에서 프로세스를 생성 할 수있는)가 생성 됩니다.

GIL에 의해 제한 될 수 있지만, 제 특별한 경우 (둘 다 테스트했습니다) , 여기서Pool 생성 된 외부 프로세스의 시작 시간 .ThreadPool


그것은 스왑 정말 쉽습니다 Processes위한 Threads. 여기 또는 여기 에서 ThreadPool솔루션 사용 방법에 대해 자세히 알아보십시오 .


Python 3.7에서 데모닉이 아닌 풀을 사용해야 할 필요성이 있었고 수락 된 답변에 게시 된 코드를 수정했습니다. 아래는 비 데모닉 풀을 생성하는 스 니펫입니다.

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 MyPool(multiprocessing.pool.Pool):
    def __init__(self, *args, **kwargs):
        kwargs['context'] = NoDaemonContext()
        super(MyPool, self).__init__(*args, **kwargs)

의 현재 구현이 multiprocessing컨텍스트를 기반으로하도록 광범위하게 리팩토링되었으므로 as 속성 NoDaemonContext있는 클래스 를 제공해야 NoDaemonProcess합니다. MyPool그러면 기본 컨텍스트 대신 해당 컨텍스트를 사용합니다.

즉,이 접근 방식에 대해 최소한 두 가지주의 사항이 있음을 경고해야합니다.

  1. 여전히 multiprocessing패키지 의 구현 세부 사항에 따라 다르 므로 언제든지 중단 될 수 있습니다.
  2. multiprocessing데모닉이 아닌 프로세스를 사용하기 어렵게 만든 타당한 이유 있습니다 . 그 중 많은 부분이 여기 에 설명되어 있습니다 . 제 생각에 가장 설득력있는 것은 :

    As for allowing children threads to spawn off children of its own using subprocess runs the risk of creating a little army of zombie 'grandchildren' if either the parent or child threads terminate before the subprocess completes and returns.


The issue I encountered was in trying to import globals between modules, causing the ProcessPool() line to get evaluated multiple times.

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

Then import safely from elsewhere in your code

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

On some Python versions replacing standart Pool to custom can raise error: AssertionError: group argument must be None for now.

Here I found a solution that can help:

class NonDaemonPool(multiprocessing.pool.Pool):
    def Process(self, *args, **kwds):
        proc = super(NonDaemonPool, self).Process(*args, **kwds)

        class NonDaemonProcess(proc.__class__):
            """Monkey-patch process to ensure it is never daemonized"""

            @property
            def daemon(self):
                return False

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

        proc.__class__ = NonDaemonProcess

        return proc

참고URL : https://stackoverflow.com/questions/6974695/python-process-pool-non-daemonic

반응형