web-dev-qa-db-fra.com

Python Process Pool non-démoniaque?

Serait-il possible de créer un pool python non démoniaque? Je veux qu'un pool puisse appeler une fonction qui contient un autre pool.

Je le veux parce que les processus Deamon ne peuvent pas créer de processus. Plus précisément, cela provoquera l'erreur:

AssertionError: daemonic processes are not allowed to have children

Par exemple, considérons le scénario où function_a a un pool qui exécute function_b qui a un pool qui exécute function_c. Cette chaîne de fonctions échouera, car function_b est exécuté dans un processus démon, et les processus démons ne peuvent pas créer de processus. 

59
Max

La classe multiprocessing.pool.Pool crée les processus de travail dans sa méthode __init__, les rend démoniaques et les démarre, et il n'est pas possible de redéfinir leur attribut daemon sur False avant leur démarrage (et par la suite, ce n'est plus autorisé). Mais vous pouvez créer votre propre sous-classe de multiprocesing.pool.Pool (multiprocessing.Pool est juste une fonction d'encapsulation) et remplacer votre propre sous-classe multiprocessing.Process, qui est toujours non démoniaque, à utiliser pour les processus de travail.

Voici un exemple complet de la procédure à suivre. Les parties importantes sont les deux classes NoDaemonProcess et MyPool en haut et d’appeler pool.close() et pool.join() sur votre instance MyPool à la fin.

#!/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()
87
Chris Arndt

Le module multitraitement dispose d’une interface Nice permettant d’utiliser des pools de processus ou. Selon votre cas d'utilisation actuel, vous pouvez envisager d'utiliser multiprocessing.pool.ThreadPool pour votre pool externe, ce qui entraînera des threads (permettant de générer des processus de l'intérieur)} par opposition à des processus.

Il est peut-être limité par la GIL, mais dans mon cas particulier (j'ai testé les deux)}, l'heure de démarrage des processus de la variable Pool externe telle que créée ici dépassait de loin la solution avec ThreadPool.


Il est très facile d’échanger Processes pour Threads. En savoir plus sur l’utilisation d’une solution ThreadPoolici ou ici .

9
timmwagener

J'avais besoin d'employer un pool non démoniaque dans Python 3.7 et j'ai fini par adapter le code affiché dans la réponse acceptée. Ci-dessous, l'extrait qui crée le pool non-démoniaque:

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)

Étant donné que l'implémentation actuelle de multiprocessing a été considérablement modifiée pour qu'elle soit basée sur les contextes, nous devons fournir une classe NoDaemonContext ayant pour attribut NoDaemonProcess. MyPool utilisera alors ce contexte au lieu de celui par défaut.

Cela dit, je dois avertir que cette approche doit comporter au moins deux mises en garde:

  1. Cela dépend toujours des détails d'implémentation du paquet multiprocessing et peut donc tomber à tout moment.
  2. Il y a des raisons valables pour lesquelles multiprocessing a rendu si difficile l'utilisation de processus non démoniques, dont beaucoup sont expliqués ici . Le plus convaincant à mon avis est: .____.

    Pour ce qui est de permettre aux enfants de donner naissance à des enfants à l’aide de le sous-processus risque de créer une petite armée de zombies 'petits-enfants' si les fils parent ou enfant se terminent avant le sous-processus se termine et retourne.

4
Massimiliano

Le problème que j'ai rencontré concernait la tentative d'importation de données globales entre modules, entraînant l'évaluation de la ligne ProcessPool () à plusieurs reprises.

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

Puis importez en toute sécurité depuis votre code.

from globals import Globals
Globals().shared_manager      
Globals().shared_process_pool
Globals().shared_thread_pool  
Globals().shared_lock         
1
James McGuigan

Sur certaines versions de Python, le remplacement de standart Pool par custom peut générer une erreur: AssertionError: group argument must be None for now.

Ici j'ai trouvé une solution qui peut aider:

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
0
Atterratio