Python Process Pool không phải là daemonic?


96

Có thể tạo một bể nuôi trăn không phải là daemonic không? Tôi muốn một hồ bơi có thể gọi một hàm có một hồ bơi khác bên trong.

Tôi muốn điều này vì các quy trình ngừng hoạt động không thể tạo quy trình. Cụ thể, nó sẽ gây ra lỗi:

AssertionError: daemonic processes are not allowed to have children

Ví dụ, hãy xem xét kịch bản trong đó function_acó một nhóm chạy function_btrong đó có một nhóm chạy function_c. Chuỗi chức năng này sẽ không thành công, vì function_bđang được chạy trong quy trình daemon và quy trình daemon không thể tạo quy trình.


AFAIK, không có nó không thể tất cả người lao động trong hồ bơi được daemonized và nó không thể tiêm phụ thuộc , BTW tôi không hiểu phần thứ hai của câu hỏi của bạn I want a pool to be able to call a function that has another pool insidevà làm thế nào mà can thiệp vào thực tế là các công nhân đang daemonized.
mouad

4
Bởi vì nếu hàm a có một nhóm chạy hàm b có một nhóm chạy hàm c, thì có một vấn đề trong b là nó đang được chạy trong một quy trình daemon và quy trình daemon không thể tạo quy trình. AssertionError: daemonic processes are not allowed to have children
Tối đa

Câu trả lời:


118

Các multiprocessing.pool.Poollớp học tạo ra các quá trình lao động trong mình __init__phương pháp, làm cho chúng ma quỉ và bắt đầu họ, và nó không thể để tái thiết lập của họ daemonthuộc tính Falsetrước khi chúng được bắt đầu (và sau đó nó không cho phép nữa). Nhưng bạn có thể tạo lớp con của riêng mình multiprocesing.pool.Pool( multiprocessing.Poolchỉ là một hàm trình bao bọc) và thay thế multiprocessing.Processlớp con của chính bạn , luôn không phải là daemonic, để được sử dụng cho các quy trình công nhân.

Đây là một ví dụ đầy đủ về cách thực hiện việc này. Các phần quan trọng là hai lớp NoDaemonProcessMyPoolở trên cùng và để gọi pool.close()pool.join()trên thể hiện của bạn MyPoolở cuối.

#!/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
Tôi vừa kiểm tra lại mã của mình với Python 2.7 / 3.2 (sau khi sửa các dòng "in") trên Linux và Python 2.6 / 2.7 / 3.2 OS X. Linux và Python 2.7 / 3.2 trên OS X hoạt động tốt nhưng mã thực sự bị treo Python 2.6 trên OS X (Lion). Đây dường như là một lỗi trong mô-đun đa xử lý, đã được sửa, nhưng tôi chưa thực sự kiểm tra trình theo dõi lỗi.
Chris Arndt

1
Cảm ơn! Trên cửa sổ bạn cũng cần phải gọimultiprocessing.freeze_support()
frmdstryr

2
Công việc tốt đẹp. Nếu ai đó đang bị rò rỉ bộ nhớ với điều này, hãy thử sử dụng "với đóng (MyPool (MyPool (process = num_cpu))) làm hồ bơi:" để xử lý hồ bơi đúng cách
Chris Lucian

31
Nhược điểm của việc sử dụng MyPoolthay vì mặc định là Poolgì? Nói cách khác, để đổi lấy sự linh hoạt khi bắt đầu các quy trình con, tôi phải trả những chi phí nào? (Nếu không có chi phí, có lẽ tiêu chuẩn Poolsẽ sử dụng các quy trình không phải daemonic).
tối đa

4
@machen Vâng, rất tiếc đó là sự thật. Trong Python 3.6, Poollớp đã được cấu trúc lại rộng rãi, do đó, Processkhông phải là một thuộc tính đơn giản nữa, mà là một phương thức, trả về cá thể quy trình mà nó nhận được từ một ngữ cảnh . Tôi đã thử ghi đè phương thức này để trả về một NoDaemonPoolphiên bản, nhưng điều này dẫn đến ngoại lệ AssertionError: daemonic processes are not allowed to have childrenkhi Pool được sử dụng.
Chris Arndt

26

Tôi có nhu cầu sử dụng một nhóm không phải daemonic trong Python 3.7 và cuối cùng đã điều chỉnh mã được đăng trong câu trả lời được chấp nhận. Dưới đây có đoạn mã tạo nhóm không phải daemonic:

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)

Vì việc triển khai hiện tại của multiprocessingđã được tái cấu trúc rộng rãi để dựa trên ngữ cảnh, chúng tôi cần cung cấp một NoDaemonContextlớp có NoDaemonProcessthuộc tính as của chúng tôi . MyPoolsau đó sẽ sử dụng ngữ cảnh đó thay vì ngữ cảnh mặc định.

Điều đó nói rằng, tôi nên cảnh báo rằng có ít nhất 2 lưu ý đối với cách tiếp cận này:

  1. Nó vẫn phụ thuộc vào chi tiết triển khai của multiprocessinggói và do đó có thể bị hỏng bất cứ lúc nào.
  2. Có những lý do xác đáng multiprocessingkhiến việc sử dụng các quy trình không phải daemonic trở nên khó khăn như vậy, nhiều trong số đó được giải thích ở đây . Điều hấp dẫn nhất theo ý kiến ​​của tôi là:

    Đối với việc cho phép các luồng con sinh ra các con của chính nó bằng cách sử dụng quy trình con có nguy cơ tạo ra một đội quân 'cháu' zombie nhỏ nếu luồng cha hoặc con chấm dứt trước khi quy trình con hoàn thành và trả về.


Về lưu ý: Trường hợp sử dụng của tôi là các nhiệm vụ song song, nhưng những đứa cháu lớn trả lại thông tin cho cha mẹ của chúng và lần lượt trả lại thông tin cho cha mẹ chúng sau khi thực hiện một số xử lý cục bộ bắt buộc. Do đó, mọi cấp / nhánh đều có một thời gian chờ rõ ràng cho tất cả các lá của nó. Cảnh báo có còn áp dụng nếu bạn rõ ràng phải đợi các quá trình được tạo ra kết thúc không?
A_A,

AttributeError: module 'multiprocessing' has no attribute 'pool'
Gặp

@Nyxynyx Đừng quênimport multiprocessing.pool
Chris Arndt

22

Các đa xử mô-đun có một giao diện đẹp để sử dụng hồ bơi với các quá trình hoặc chủ đề. Tùy thuộc vào trường hợp sử dụng hiện tại của bạn, bạn có thể cân nhắc việc sử dụng multiprocessing.pool.ThreadPoolcho Pool bên ngoài của mình, điều này sẽ dẫn đến các luồng (cho phép tạo ra các quy trình từ bên trong) trái ngược với các quy trình.

Nó có thể bị giới hạn bởi GIL, nhưng trong trường hợp cụ thể của tôi (tôi đã thử nghiệm cả hai) , thời gian khởi động cho các quy trình từ bên ngoài Poolnhư được tạo ở đây vượt xa giải pháp ThreadPool.


Nó thực sự dễ dàng để hoán đổi Processescho Threads. Đọc thêm về cách sử dụng ThreadPoolgiải pháp tại đây hoặc tại đây .


Cảm ơn - điều này đã giúp tôi rất nhiều - cách sử dụng tuyệt vời của chuỗi ở đây (để tạo ra các quy trình thực sự hoạt động tốt)
trance_dude

1
Đối với những người đang tìm kiếm một giải pháp thực tế có thể áp dụng cho tình huống của họ, thì đây chính là giải pháp.
abanana

6

Trên một số phiên bản Python thay thế tiêu chuẩn Pool để tùy chỉnh có thể nâng lỗi: AssertionError: group argument must be None for now.

Ở đây tôi đã tìm thấy một giải pháp có thể giúp:

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.ProcessPoolExecutorkhông có giới hạn này. Nó có thể có một nhóm quy trình lồng nhau mà không có vấn đề gì:

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

Đoạn mã trình diễn trên đã được thử nghiệm với Python 3.8.

Tín dụng: câu trả lời của jfs


1
Đây rõ ràng là giải pháp tốt nhất hiện nay, vì nó yêu cầu những thay đổi tối thiểu.
DreamFlasher

1
hoạt động hoàn hảo! ... như một ghi chú phụ bằng cách sử dụng con- multiprocessing.Poolbên trong a ProcessPoolExecutor.Poolcũng có thể!
raphael

3

Vấn đề tôi gặp phải là khi cố gắng nhập các hình cầu giữa các mô-đun, khiến dòng ProcessPool () được đánh giá nhiều lần.

Gloals.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

Sau đó nhập một cách an toàn từ nơi khác trong mã của bạn

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

2

Tôi đã thấy mọi người giải quyết vấn đề này bằng cách sử dụng celerycái nĩa multiprocessingđược gọi là bida (phần mở rộng hồ bơi đa xử lý), cho phép các quá trình daemonic sinh ra con cái. Cách giải quyết đơn giản là thay thế multiprocessingmô-đun bằng cách:

import billiard as multiprocessing
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.