Python Process Pool ไม่ใช่ daemonic?


105

เป็นไปได้ไหมที่จะสร้าง python Pool ที่ไม่ใช่ daemonic? ฉันต้องการสระว่ายน้ำที่สามารถเรียกใช้ฟังก์ชันที่มีสระว่ายน้ำอื่นอยู่ข้างในได้

ฉันต้องการสิ่งนี้เนื่องจากกระบวนการ deamon ไม่สามารถสร้างกระบวนการได้ โดยเฉพาะจะทำให้เกิดข้อผิดพลาด:

AssertionError: daemonic processes are not allowed to have children

ยกตัวอย่างเช่นพิจารณาสถานการณ์ที่function_aมีสระว่ายน้ำซึ่งวิ่งที่มีสระว่ายน้ำที่วิ่งfunction_b function_cห่วงโซ่ฟังก์ชันนี้จะล้มเหลวเนื่องจากfunction_bกำลังรันในกระบวนการ daemon และกระบวนการ daemon ไม่สามารถสร้างกระบวนการได้


AFAIK ไม่เป็นไปได้ที่คนงานทุกคนในพูลจะถูก daemonized และเป็นไปไม่ได้ที่จะฉีดการพึ่งพา BTW ฉันไม่เข้าใจส่วนที่สองของคำถามของคุณI want a pool to be able to call a function that has another pool insideและวิธีที่รบกวนความจริงที่ว่าคนงานถูก daemonized
mouad

5
เนื่องจากถ้าฟังก์ชัน a มีพูลที่รันฟังก์ชัน b ซึ่งมีพูลที่รันฟังก์ชัน c จะมีปัญหาใน b ที่รันในกระบวนการดีมอนและกระบวนการ daemon ไม่สามารถสร้างโปรเซสได้ AssertionError: daemonic processes are not allowed to have children
สูงสุด

คำตอบ:


127

multiprocessing.pool.Poolชั้นสร้างกระบวนการปฏิบัติงานในของ__init__วิธีการที่ทำให้พวกเขาถูกผีเข้าและเริ่มพวกเขาและมันเป็นไปไม่ได้ที่จะตั้งใหม่ของพวกเขาdaemonแอตทริบิวต์Falseก่อนที่พวกเขาจะเริ่มต้น (และหลังจากนั้นจะไม่ได้รับอนุญาตอีกต่อไป) แต่คุณสามารถสร้างคลาสย่อยของคุณเองmultiprocesing.pool.Pool( multiprocessing.Poolเป็นเพียงฟังก์ชัน wrapper) และแทนที่multiprocessing.Processคลาสย่อยของคุณเองซึ่งไม่ใช่ daemonic เสมอเพื่อใช้สำหรับกระบวนการของผู้ปฏิบัติงาน

นี่คือตัวอย่างทั้งหมดของวิธีการดำเนินการนี้ ส่วนที่สำคัญคือสองคลาส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
ฉันเพิ่งทดสอบโค้ดของฉันอีกครั้งด้วย Python 2.7 / 3.2 (หลังจากแก้ไขบรรทัด "print") บน Linux และ Python 2.6 / 2.7 / 3.2 OS X Linux และ Python 2.7 / 3.2 บน OS X ทำงานได้ดี แต่โค้ดนั้นค้างแน่นอน Python 2.6 บน OS X (Lion) นี่ดูเหมือนจะเป็นข้อบกพร่องในโมดูลการประมวลผลหลายขั้นตอนซึ่งได้รับการแก้ไข แต่ฉันไม่ได้ตรวจสอบตัวติดตามข้อผิดพลาดจริงๆ
Chris Arndt

1
ขอบคุณ! บน windows คุณต้องโทรmultiprocessing.freeze_support()
frmdstryr

2
ทำได้ดีมาก หากใครมีปัญหาหน่วยความจำรั่วให้ลองใช้ "with closed (MyPool (กระบวนการ = num_cpu)) เป็นพูล:" เพื่อกำจัดสระว่ายน้ำอย่างถูกต้อง
Chris Lucian

33
ข้อเสียของการใช้MyPoolแทนค่าเริ่มต้นPoolคืออะไร? กล่าวอีกนัยหนึ่งคือเพื่อแลกกับความยืดหยุ่นในการเริ่มต้นกระบวนการย่อยฉันต้องจ่ายค่าใช้จ่ายอะไรบ้าง? (หากไม่มีค่าใช้จ่ายเป็นไปได้ว่ามาตรฐานPoolจะใช้กระบวนการที่ไม่ใช่ daemonic)
สูงสุด

4
@machen ใช่น่าเสียดายที่เป็นเรื่องจริง ในหลาม 3.6 Poolระดับได้รับการ refactored อย่างกว้างขวางจึงProcessไม่ได้เป็นแอตทริบิวต์ง่ายอีกต่อไป แต่เป็นวิธีการซึ่งจะส่งกลับเช่นกระบวนการที่จะได้รับจากบริบท ฉันพยายามเขียนทับวิธีนี้เพื่อส่งคืนNoDaemonPoolอินสแตนซ์ แต่ผลลัพธ์นี้เป็นข้อยกเว้นAssertionError: daemonic processes are not allowed to have childrenเมื่อใช้พูล
Chris Arndt

32

ฉันมีความจำเป็นที่จะต้องใช้พูลที่ไม่ใช่ daemonic ใน Python 3.7 และลงเอยด้วยการปรับโค้ดที่โพสต์ในคำตอบที่ยอมรับ ด้านล่างนี้มีตัวอย่างข้อมูลที่สร้างพูลที่ไม่ใช่ daemonic:

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)

ตามการใช้งานปัจจุบันของ multiprocessingได้รับการปรับโครงสร้างใหม่อย่างกว้างขวางเพื่อให้อิงตามบริบทเราจึงจำเป็นต้องจัดเตรียมNoDaemonContextคลาสที่มีNoDaemonProcessคุณลักษณะเป็นของเรา NestablePoolจะใช้บริบทนั้นแทนค่าเริ่มต้น

ที่กล่าวว่าฉันควรเตือนว่ามีข้อแม้อย่างน้อยสองประการสำหรับแนวทางนี้:

  1. ยังคงขึ้นอยู่กับรายละเอียดการใช้งานของไฟล์ multiprocessingแพ็กเกจดังนั้นจึงอาจแตกได้ตลอดเวลา
  2. มีเหตุผลที่ถูกต้องมีเหตุผลที่multiprocessingทำให้มันเป็นเรื่องยากมากที่จะใช้กระบวนการที่ไม่ใช่ปิศาจหลายแห่งซึ่งมีการอธิบายที่นี่ สิ่งที่น่าสนใจที่สุดในความคิดของฉันคือ:

สำหรับการอนุญาตให้เธรดเด็กวางไข่เด็กของตัวเองโดยใช้กระบวนการย่อยจะมีความเสี่ยงในการสร้างกองทัพซอมบี้ตัวเล็ก ๆ 'หลาน' หากเธรดแม่หรือเด็กยุติก่อนที่กระบวนการย่อยจะเสร็จสมบูรณ์และส่งคืน


1
เกี่ยวกับข้อแม้: กรณีการใช้งานของฉันเป็นงานคู่ขนานกัน แต่ลูกหลานกลับส่งข้อมูลให้พ่อแม่ของพวกเขาซึ่งจะส่งคืนข้อมูลให้พ่อแม่ของพวกเขาหลังจากดำเนินการบางอย่างที่จำเป็นในท้องถิ่น ดังนั้นทุกระดับ / สาขามีการรออย่างชัดเจนสำหรับใบไม้ทั้งหมด ข้อแม้ยังคงใช้อยู่หรือไม่หากคุณต้องรอให้กระบวนการเกิดเสร็จสิ้นอย่างชัดเจน?
A_A

คุณช่วยเพิ่มวิธีใช้สิ่งนี้แทนการประมวลผลหลายพูลได้หรือไม่?
วิทยุควบคุม

"ตอนนี้คุณสามารถใช้มัลติโพรเซสซิงก์พูลและ NestablePool แทนกันได้"
วิทยุควบคุม

24

multiprocessingโมดูลมีอินเตอร์เฟซที่ดีที่จะใช้สระว่ายน้ำกับกระบวนการหรือหัวข้อ ขึ้นอยู่กับกรณีการใช้งานปัจจุบันของคุณคุณอาจพิจารณาใช้multiprocessing.pool.ThreadPoolสำหรับพูลภายนอกของคุณซึ่งจะส่งผลให้เกิดเธรด(ที่อนุญาตให้เกิดกระบวนการจากภายใน)ซึ่งตรงข้ามกับกระบวนการ

มันอาจจะถูก จำกัด โดย GIL แต่ในกรณีโดยเฉพาะอย่างยิ่งของฉัน(ผมทดสอบทั้งสอง) , เวลาเริ่มต้นสำหรับกระบวนการจากภายนอกPoolเป็นสร้างที่นี่ThreadPoolให้ห่างไกลในแง่แก้ปัญหาด้วย


มันเป็นเรื่องง่ายที่จะแลกเปลี่ยนสำหรับProcesses Threadsอ่านเพิ่มเติมเกี่ยวกับวิธีการใช้ThreadPoolวิธีการแก้ปัญหาที่นี่หรือที่นี่


ขอบคุณ - สิ่งนี้ช่วยฉันได้มาก - การใช้เธรดที่นี่อย่างดีเยี่ยม (เพื่อกระบวนการวางไข่ซึ่งทำงานได้ดีจริง ๆ )
trance_dude

1
สำหรับผู้ที่กำลังมองหาวิธีแก้ปัญหาที่ใช้ได้จริงซึ่งอาจใช้ได้กับสถานการณ์ของพวกเขานี่คือวิธีหนึ่ง
abanana

6

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

5

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

ข้อ จำกัด ของแต่เป็นว่ามันไม่ได้มีProcessPoolExecutor maxtasksperchildหากคุณต้องการสิ่งนี้ให้พิจารณาคำตอบของ Massimilianoแทน

เครดิต: ตอบโดย jfs


1
ตอนนี้ถือเป็นทางออกที่ดีที่สุดอย่างชัดเจนเนื่องจากต้องมีการเปลี่ยนแปลงเพียงเล็กน้อย
DreamFlasher

1
ทำงานได้อย่างสมบูรณ์แบบ! ... เป็นโน้ตด้านข้างโดยใช้เด็กmultiprocessing.Poolข้างใน a ProcessPoolExecutor.Poolก็เป็นไปได้เช่นกัน!
raphael

4

ปัญหาที่ฉันพบคือในการพยายามนำเข้า globals ระหว่างโมดูลทำให้บรรทัด 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         

ฉันได้เขียนคลาส Wrapper เพิ่มเติมpathos.multiprocessingที่นี่:

โปรดทราบว่าหาก usecase ของคุณต้องการเพียง async multiprocess map เป็นการเพิ่มประสิทธิภาพ joblib จะจัดการกลุ่มกระบวนการทั้งหมดของคุณในเบื้องหลังและอนุญาตให้ใช้ไวยากรณ์ที่เรียบง่ายนี้:

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

3

ฉันได้เห็นคนที่เกี่ยวข้องกับปัญหานี้โดยใช้celery's แยกของmultiprocessingที่เรียกว่าบิลเลียด (multiprocessing ส่วนขยายสระว่ายน้ำ) ซึ่งช่วยให้กระบวนการปิศาจกับเด็กวางไข่ การแก้ปัญหาคือการเปลี่ยนmultiprocessingโมดูลโดย:

import billiard as multiprocessing

0

สิ่งนี้นำเสนอวิธีแก้ปัญหาเมื่อข้อผิดพลาดดูเหมือนเป็นบวกเท็จ ขณะที่ยังตั้งข้อสังเกตโดยเจมส์นี้สามารถเกิดขึ้นได้กับผู้ที่ไม่ได้ตั้งใจนำเข้าจากกระบวนการที่ถูกผีเข้า

ตัวอย่างเช่นหากคุณมีรหัสง่ายๆดังต่อไปนี้คุณWORKER_POOLสามารถนำเข้าจากผู้ปฏิบัติงานโดยไม่ได้ตั้งใจซึ่งนำไปสู่ข้อผิดพลาด

import multiprocessing

WORKER_POOL = multiprocessing.Pool()

แนวทางที่เรียบง่าย แต่เชื่อถือได้สำหรับวิธีแก้ปัญหาคือ:

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

ในวิธีแก้ปัญหาข้างต้นMyClass.worker_poolสามารถใช้ได้โดยไม่มีข้อผิดพลาด หากคุณคิดว่าแนวทางนี้สามารถปรับปรุงได้โปรดแจ้งให้เราทราบ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.