Пул процесів Python недемонічний?


96

Чи можна було б створити пул пітонів, який не є демонічним? Я хочу, щоб пул міг викликати функцію, яка має інший пул всередині.

Я хочу цього, оскільки процеси deamon не можуть створити процес. Зокрема, це призведе до помилки:

AssertionError: daemonic processes are not allowed to have children

Наприклад, розглянемо сценарій, де function_aє пул, який працює, function_bякий має пул, який працює function_c. Цей функціональний ланцюг вийде з ладу, оскільки function_bвін запускається в процесі демона, і процеси демони не можуть створювати процеси.


AFAIK, ні, це неможливо, щоб усі працівники пулу демонізовані, і неможливо ввести залежність. До речі, я не розумію другу частину Вашого запитання I want a pool to be able to call a function that has another pool insideі як це заважає тому, що працівники демонізовані.
mouad

4
Оскільки, якщо функція a має пул, який запускає функцію b, який має пул, який запускає функцію c, у b проблема полягає в тому, що вона запускається в процесі демона, і процеси демони не можуть створювати процеси. AssertionError: daemonic processes are not allowed to have children
Макс

Відповіді:


118

multiprocessing.pool.PoolКлас створює робочі процеси в його __init__методі, робить їх демонами і запускає їх, і це не можливо повторно встановити свій daemonатрибут , Falseперш ніж вони почали (і після цього він не має більше). Але ви можете створити свій власний підклас multiprocesing.pool.Pool( multiprocessing.Poolце просто функція обгортки) і замінити свій власнийmultiprocessing.Process підклас, який завжди не є демонічним, для використання у робочих процесах.

Ось повний приклад того, як це зробити. Важливі частини є два класи 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 (після виправлення рядків "друку") на 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
Дякую! На вікна також потрібно зателефонуватиmultiprocessing.freeze_support()
frmdstryr

2
Хороша робота. Якщо хтось отримує витік пам'яті за допомогою цього, спробуйте використати "з закриттям (MyPool (procesi = num_cpu)) як пул:", щоб правильно розпоряджатися пулом
Кріс Люсіан

31
Які недоліки використання MyPoolзамість типового Pool? Іншими словами, які гроші я плачу в обмін на гнучкість запуску дочірніх процесів? (Якби не було витрат, мабуть, стандарт Poolвикористовував би недемонічні процеси).
більше

4
@machen Так, на жаль це правда. У Python 3.6 Poolклас значно реконструйований, тому Processце вже не простий атрибут, а метод, який повертає екземпляр процесу, який він отримує з контексту . Я спробував перезаписати цей метод, щоб повернути NoDaemonPoolекземпляр, але це призводить до винятку, AssertionError: daemonic processes are not allowed to have childrenколи використовується пул.
Кріс Арндт

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широко реконструйована на основі контекстів, нам потрібно надати NoDaemonContextклас, який має наш NoDaemonProcessатрибут.MyPoolтоді використовуватиме цей контекст замість типового.

Тим не менш, я повинен попередити, що існує щонайменше 2 застереження щодо такого підходу:

  1. Це все ще залежить від деталей реалізації multiprocessing пакету, і тому може зламатись у будь-який час.
  2. Існують вагомі причини, чому multiprocessingтак важко було використовувати недемонічні процеси, багато з яких пояснюються тут . На мою думку, найбільш переконливим є:

    Що стосується дозволу дочірнім потокам нереститися власним дітям за допомогою підпроцесу, то існує ризик створити невелику армію зомбі-онуків, якщо батьківський або дочірній потоки закінчуються до завершення та повернення підпроцесу.


Що стосується застереження: Мій варіант використання parallelising завдання, але онуки повертають інформацію своїм батькам , що в свою чергу , інформацію повернення до своїх батьків після того, як роблять деякі необхідну місцеву обробку. Отже, на кожному рівні / гілці є явне очікування для всіх своїх листків. Чи все ще застосовується застереження, якщо вам явно потрібно чекати завершення породжених процесів?
A_A

Отримання помилки AttributeError: module 'multiprocessing' has no attribute 'pool'в Python
3.8.0

@Nyxynyx Не забувайтеimport multiprocessing.pool
Кріс Арндт,

22

Багатопроцесорний модуль має інтерфейс зручно використовувати басейни з процесами або потоками. Залежно від вашого поточного випадку використання, ви можете розглянути можливість використання multiprocessing.pool.ThreadPoolдля зовнішнього пулу, що призведе до потоків (які дозволяють створювати процеси зсередини) на відміну від процесів.

Це може бути обмежено GIL, але в моєму конкретному випадку (я протестував обидва) , час запуску процесів із зовнішнього, Poolяк створено тут, значно переважав рішення ThreadPool.


Це дійсно легко своп Processesдля Threads. Детальніше про те, як використовувати ThreadPoolрішення, читайте тут або тут .


Дякую - це мені дуже допомогло - велике використання потоків тут (для створення процесів, які насправді добре працюють)
trance_dude

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всередині a 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форк з multiprocessingназвою більярд (багатопроцесорні розширення пулу), який дозволяє демонічним процесам народжувати дітей. Вирішення полягає в тому, щоб просто замінити multiprocessingмодуль на:

import billiard as multiprocessing
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.