web-dev-qa-db-fra.com

Python partageant un verrou entre les processus

J'essaie d'utiliser une fonction partielle pour que pool.map () puisse cibler une fonction ayant plusieurs paramètres (dans ce cas, un objet Lock ()).

Voici un exemple de code (tiré d'une réponse à une de mes questions précédentes):

from functools import partial

def target(lock, iterable_item):
    for item in items:
        # Do cool stuff
        if (... some condition here ...):
            lock.acquire()
            # Write to stdout or logfile, etc.
            lock.release()

def main():
    iterable = [1, 2, 3, 4, 5]
    pool = multiprocessing.Pool()
    l = multiprocessing.Lock()
    func = partial(target, l)
    pool.map(func, iterable)
    pool.close()
    pool.join()

Cependant, quand je lance ce code, j'obtiens l'erreur:

Runtime Error: Lock objects should only be shared between processes through inheritance.

Qu'est-ce que j'oublie ici? Comment puis-je partager le verrou entre mes sous-processus?

36
DJMcCarthy12

Désolé, j'aurais dû prendre cela dans ma réponse à votre autre question. Vous ne pouvez pas transmettre les objets multiprocessing.Lock normaux aux méthodes Pool, car ils ne peuvent pas être traités. Il y a deux façons de contourner ce problème. L’une consiste à créer Manager() et à transmettre un Manager.Lock() :

def main():
    iterable = [1, 2, 3, 4, 5]
    pool = multiprocessing.Pool()
    m = multiprocessing.Manager()
    l = m.Lock()
    func = partial(target, l)
    pool.map(func, iterable)
    pool.close()
    pool.join()

C'est un peu lourd, cependant; L'utilisation de Manager nécessite la création d'un autre processus pour héberger le serveur Manager. Et tous les appels à acquire/release le verrou doivent être envoyés à ce serveur via IPC.

L'autre option consiste à transmettre le multiprocessing.Lock() au moment de la création du pool, à l'aide de initializer kwarg. Cela rendra votre instance de verrouillage globale dans tous les enfants travailleurs:

def target(iterable_item):
    for item in items:
        # Do cool stuff
        if (... some condition here ...):
            lock.acquire()
            # Write to stdout or logfile, etc.
            lock.release()
def init(l):
    global lock
    lock = l

def main():
    iterable = [1, 2, 3, 4, 5]
    l = multiprocessing.Lock()
    pool = multiprocessing.Pool(initializer=init, initargs=(l,))
    pool.map(target, iterable)
    pool.close()
    pool.join()

La deuxième solution a pour effet secondaire de ne plus requérir partial.

67
dano

Voici une version (utilisant Barrier au lieu de Lock, mais vous en avez une idée) qui fonctionnerait également sous Windows (où la fork manquante cause des problèmes supplémentaires):

import multiprocessing as mp

def procs(uid_barrier):
    uid, barrier = uid_barrier
    print(uid, 'waiting')
    barrier.wait()
    print(uid, 'past barrier')    

def main():
    N_PROCS = 10
    with mp.Manager() as man:
        barrier = man.Barrier(N_PROCS)
        with mp.Pool(N_PROCS) as p:
            p.map(procs, ((uid, barrier) for uid in range(N_PROCS)))

if __== '__main__':
    mp.freeze_support()
    main()
0
Tom Pohl