Pythonプロセスプールは非デーモンですか?


96

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

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

AssertionError: daemonic processes are not allowed to have children

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


私の知る限り、プール内のすべてのワーカーをデーモン化することは不可能であり、依存関係挿入することもできません。ところで、質問の2番目の部分I want a pool to be able to call a function that has another pool insideと、ワーカーがデーモン化されているという事実にどのように干渉するのか理解できません。
mouad 2011

4
関数aに、関数cを実行するプールを持つ関数bを実行するプールがある場合、bにはデーモンプロセスで実行され、デーモンプロセスがプロセスを作成できないという問題があります。AssertionError: daemonic processes are not allowed to have children
最大

回答:


118

multiprocessing.pool.Poolクラスは、その中のワーカープロセスを作成__init__鬼神のそれらを作り、それらを起動し、メソッドを、彼らの再設定することはできませんdaemonに属性をFalse、彼らが開始され(そしてその後それはもう許されていない)の前に。しかし、独自のサブクラスmultiprocesing.pool.Poolmultiprocessing.Pool単なるラッパー関数です)を作成multiprocessing.Processし、ワーカープロセスに使用するために、常に非デーモンである独自のサブクラスに置き換えることができます。

これを行う方法の完全な例を次に示します。重要な部分は、2つのクラスですNoDaemonProcessし、MyPool一番上にして呼び出すように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()

1
LinuxのPython 2.7 / 3.2(「印刷」行を修正した後)でコードを再度テストしたところ、LinuxとPython 2.6 / 2.7 / 3.2 OS Xで動作しました。LinuxとOS XのPython 2.7 / 3.2は正常に動作しますが、コードは実際にハングします。 OS X(Lion)上のPython 2.6。これは修正されたマルチプロセッシングモジュールのバグのようですが、実際にバグトラッカーを確認していません。
Chris Arndt 2012

1
ありがとう!Windowsでも呼び出す必要がありますmultiprocessing.freeze_support()
frmdstryr 14年

2
よくやった。これで誰かがメモリリークを起こしている場合は、「with close(MyPool(processes = num_cpu))as pool:」を使用してプールを適切に廃棄してください
Chris Lucian

31
MyPoolデフォルトの代わりに使用することの欠点は何Poolですか?言い換えれば、子プロセスを開始する柔軟性と引き換えに、どのようなコストを支払う必要がありますか?(コストがなければ、おそらく標準Poolは非デーモンプロセスを使用したはずです)。
最大

4
@machenはい、残念ながらそうです。Python 3.6では、Poolクラスが大幅にリファクタリングされたためProcess、単純な属性ではなく、コンテキストから取得したプロセスインスタンスを返すメソッドです。このメソッドを上書きしてNoDaemonPoolインスタンスを返すことを試みましAssertionError: daemonic processes are not allowed to have childrenたが、プールを使用すると例外が発生します。
Chris Arndt

26

私は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次に、デフォルトのコンテキストの代わりにそのコンテキストを使用します。

とはいえ、このアプローチには少なくとも2つの警告があることに注意してください。

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

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


注意点:私のユースケースはタスクの並列化ですが、孫情報を返し、次に必要なローカル処理を行った後に情報を返します。その結果、すべてのレベル/ブランチには、そのすべての葉に対する明示的な待機があります。生成されたプロセスが完了するのを明示的に待つ必要がある場合でも、警告は適用されますか?
A_A

AttributeError: module 'multiprocessing' has no attribute 'pool'Python 3.8.0でエラーを取得する
Nyxynyx

@Nyxynyx忘れないでimport multiprocessing.pool
クリス・アーント

22

マルチプロセッシングモジュールは、プロセスとプールを使用するにはすてきなインターフェイスがあるのスレッドを。現在のユースケースによってmultiprocessing.pool.ThreadPoolは、外部プールに使用することを検討する場合があります。これにより、プロセスではなくスレッド(内部からプロセスを生成できるようになります)になります。

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


と交換Processesするのは本当に簡単ですThreadsThreadPoolソリューションの使用方法の詳細については、こちらまたはこちらをご覧ください


おかげで-これは私に大いに役立ちました-ここでスレッドをうまく使用する(実際にパフォーマンスの高いプロセスを生成する)
trance_dude

1
おそらく自分の状況に当てはまる実用的な解決策を探している人にとって、これが1つの解決策です。
アバナナ

6

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

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

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.ProcessPoolExecutorこの制限はありません。それはまったく問題なくネストされたプロセスプールを持つことができます:

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

上記のデモコードはPython 3.8でテストされています。

クレジット:jfsによる回答


1
変更が最小限で済むため、これは明らかに最良のソリューションです。
DreamFlasher

1
完璧に動作します!...子を使った副注として- multiprocessing.Pool内部ProcessPoolExecutor.Poolでも可能です!
ラファエル

3

私が遭遇した問題は、モジュール間でグローバルをインポートしようとして、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         

2

デーモンプロセスが子を生成できるようにする、呼び出されたビリヤード(マルチプロセッシングプール拡張)celeryのforkのフォークを使用してこの問題に対処している人を見てきました。ウォークアラウンドは、モジュールを単に次のように置き換えることです:multiprocessingmultiprocessing

import billiard as multiprocessing
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.