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
_サブクラスを置き換えることができます。ワーカープロセス用。
これを行う方法の完全な例を次に示します。重要な部分は、2つのクラスNoDaemonProcess
とMyPool
が最上部にあり、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()
_
multiprocessing モジュールには、プロセスまたはスレッドでプールを使用するNiceインターフェースがあります。現在のユースケースに応じて、multiprocessing.pool.ThreadPool
は、外部プールに対して、プロセスではなくスレッド(内部からプロセスを生成できるようにする)になります。
GILによって制限される場合がありますが、私の特定の場合(私は両方をテストしました)、作成された外部Pool
からのプロセスの起動時間- ここ ソリューションはThreadPool
をはるかに上回りました。
Processes
をThreads
に交換するのは本当に簡単です。 ThreadPool
ソリューションを使用する方法について詳しく読む here または here 。
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つの注意事項があることに注意する必要があります。
multiprocessing
パッケージの実装の詳細に依存しているため、いつでも破損する可能性があります。multiprocessing
が非デーモンプロセスの使用を非常に難しくした正当な理由があります。その多くは here で説明されています。私の意見で最も説得力があるのは:子スレッドがサブプロセスを使用して独自の子を生成できるようにするため、サブプロセスが完了して戻る前に親または子スレッドのいずれかが終了すると、ゾンビ「孫」の小さな軍隊を作成するリスクがあります。
私が遭遇した問題は、モジュール間でグローバルをインポートしようとして、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
一部の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