web-dev-qa-db-ja.com

Python Process Pool non-daemonic?

python非デーモンのプールを作成することは可能ですか?プール内に別のプールを持つ関数を呼び出せるようにしたいのですが。

デーモンプロセスがプロセスを作成できないため、これが必要です。具体的には、エラーが発生します。

AssertionError: daemonic processes are not allowed to have children

たとえば、function_aを実行するプールがあるfunction_bfunction_cを実行するプールがあるシナリオを考えます。 function_bはデーモンプロセスで実行されており、デーモンプロセスはプロセスを作成できないため、この関数チェーンは失敗します。

74
Max

_multiprocessing.pool.Pool_クラスは、その___init___メソッドでワーカープロセスを作成し、それらをデーモン化し、それらを開始します。そして、daemon属性をFalseに再設定することはできませんそれらは開始されます(その後は許可されなくなります)。ただし、_multiprocesing.pool.Pool_の独自のサブクラスを作成し(_multiprocessing.Pool_は単なるラッパー関数です)、使用する独自の_multiprocessing.Process_サブクラスを置き換えることができます。ワーカープロセス用。

これを行う方法の完全な例を次に示します。重要な部分は、2つのクラスNoDaemonProcessMyPoolが最上部にあり、MyPoolインスタンスのpool.close()pool.join()を最後に呼び出すことです。 。

_#!/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 __== '__main__':
    test()
_
99
Chris Arndt

multiprocessing モジュールには、プロセスまたはスレッドでプールを使用するNiceインターフェースがあります。現在のユースケースに応じて、multiprocessing.pool.ThreadPoolは、外部プールに対して、プロセスではなくスレッド(内部からプロセスを生成できるようにする)になります。

GILによって制限される場合がありますが、私の特定の場合(私は両方をテストしました)、作成された外部Poolからのプロセスの起動時間- ここ ソリューションはThreadPoolをはるかに上回りました。


ProcessesThreadsに交換するのは本当に簡単です。 ThreadPoolソリューションを使用する方法について詳しく読む here または here

15
timmwagener

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の現在の実装は、コンテキストに基づいて広範囲にリファクタリングされているため、属性としてNoDaemonContextを持つNoDaemonProcessクラスを提供する必要があります。 MyPoolは、デフォルトのコンテキストの代わりにそのコンテキストを使用します。

とはいえ、このアプローチには少なくとも2つの注意事項があることに注意する必要があります。

  1. multiprocessingパッケージの実装の詳細に依存しているため、いつでも破損する可能性があります。
  2. multiprocessingが非デーモンプロセスの使用を非常に難しくした正当な理由があります。その多くは here で説明されています。私の意見で最も説得力があるのは:

    子スレッドがサブプロセスを使用して独自の子を生成できるようにするため、サブプロセスが完了して戻る前に親または子スレッドのいずれかが終了すると、ゾンビ「孫」の小さな軍隊を作成するリスクがあります。

15
Massimiliano

私が遭遇した問題は、モジュール間でグローバルをインポートしようとして、ProcessPool()行が複数回評価されることでした。

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

次に、コードの他の場所から安全にインポートします

from globals import Globals
Globals().shared_manager      
Globals().shared_process_pool
Globals().shared_thread_pool  
Globals().shared_lock         
3
James McGuigan

一部のPythonバージョンでは、スタンダートプールをカスタムに置き換えると、エラーが発生する場合があります:AssertionError: group argument must be None for now

ここ 私は助けることができる解決策を見つけました:

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
3
Atterratio