web-dev-qa-db-fra.com

Comment le multiprocessing.Manager () fonctionne-t-il en python?

J'ai une préoccupation concernant le multiprocessing.Manager () en python, voici l'exemple,

import multiprocessing 

def f(ns):

    ns.x *=10
    ns.y *= 10

if __== '__main__':
    manager = multiprocessing.Manager()
    ns = manager.Namespace()
    ns.x = 1
    ns.y = 2

    print 'before', ns
    p = multiprocessing.Process(target=f, args=(ns,))
    p.start()
    p.join()
    print 'after', ns

et la sortie est,

before Namespace(x=1, y=2)
after Namespace(x=10, y=20)

Jusqu'à présent, cela fonctionnait comme prévu, puis j'ai modifié le code comme ceci,

import multiprocessing 

def f(ns):

    ns.x.append(10)
    ns.y.append(10)

if __== '__main__':
    manager = multiprocessing.Manager()
    ns = manager.Namespace()
    ns.x = []
    ns.y = []

    print 'before', ns
    p = multiprocessing.Process(target=f, args=(ns,))
    p.start()
    p.join()
    print 'after', ns

maintenant, la sortie est,

before Namespace(x=[], y=[])
after Namespace(x=[], y=[])

Cela m'a dérouté pourquoi la liste n'a pas été modifiée comme prévu? quelqu'un peut-il m'aider à comprendre ce qui s'est passé? Merci d'avance!

49
user1231470

Les objets proxy du gestionnaire ne peuvent pas propager les modifications apportées aux objets mutables (non gérés) à l'intérieur d'un conteneur. En d'autres termes, si vous avez un objet manager.list(), toutes les modifications apportées à la liste gérée elle-même sont propagées à tous les autres processus. Mais si vous avez une liste normale Python à l'intérieur de cette liste, aucune modification de la liste intérieure ne se propage, car le gestionnaire n'a aucun moyen de détecter le changement.

Afin de propager les modifications, vous devez également utiliser les objets manager.list() pour les listes imbriquées (nécessite Python 3.6 ou plus récent ), ou vous devez modifier la fonction manager.list() objet directement (voir la note sur manager.list en Python 3.5 ou plus ancien ).

Par exemple, considérez le code suivant et sa sortie:

import multiprocessing
import time

def f(ns, ls, di):
    ns.x += 1
    ns.y[0] += 1
    ns_z = ns.z
    ns_z[0] += 1
    ns.z = ns_z

    ls[0] += 1
    ls[1][0] += 1 # unmanaged, not assigned back
    ls_2 = ls[2]  # unmanaged...
    ls_2[0] += 1
    ls[2] = ls_2  # ... but assigned back
    ls[3][0] += 1 # managed, direct manipulation

    di[0] += 1
    di[1][0] += 1 # unmanaged, not assigned back
    di_2 = di[2]  # unmanaged...
    di_2[0] += 1
    di[2] = di_2  # ... but assigned back
    di[3][0] += 1 # managed, direct manipulation

if __== '__main__':
    manager = multiprocessing.Manager()
    ns = manager.Namespace()
    ns.x = 1
    ns.y = [1]
    ns.z = [1]
    ls = manager.list([1, [1], [1], manager.list([1])])
    di = manager.dict({0: 1, 1: [1], 2: [1], 3: manager.list([1])})

    print('before', ns, ls, ls[2], di, di[2], sep='\n')
    p = multiprocessing.Process(target=f, args=(ns, ls, di))
    p.start()
    p.join()
    print('after', ns, ls, ls[2], di, di[2], sep='\n')

Sortie:

before
Namespace(x=1, y=[1], z=[1])
[1, [1], [1], <ListProxy object, typeid 'list' at 0x10b8c4630>]
[1]
{0: 1, 1: [1], 2: [1], 3: <ListProxy object, typeid 'list' at 0x10b8c4978>}
[1]
after
Namespace(x=2, y=[1], z=[2])
[2, [1], [2], <ListProxy object, typeid 'list' at 0x10b8c4630>]
[2]
{0: 2, 1: [1], 2: [2], 3: <ListProxy object, typeid 'list' at 0x10b8c4978>}
[2]

Comme vous pouvez le voir, lorsqu'une nouvelle valeur est affectée directement au conteneur géré, elle change; lorsqu'il est affecté à un conteneur modifiable dans le conteneur géré, il ne change pas; mais si le conteneur modifiable est ensuite réaffecté au conteneur géré, il change à nouveau. L'utilisation d'un conteneur géré imbriqué fonctionne également, détectant directement les modifications sans avoir à les affecter de nouveau au conteneur parent.

51
senderle

ns est une instance de NamespaceProxy. Ces objets ont des __getattr__, __setattr__, et __delattr__ méthodes qui permettent de partager des valeurs entre les processus. Afin de profiter de ce mécanisme lors de la modification d'une valeur, vous devez déclencher __setattr__.

ns.x.append(10)

provoque ns.__getattr__ à appeler pour récupérer ns.x, mais cela ne provoque pas ns.__setattr__ être appelé.

Pour résoudre ce problème, vous devez utiliser ns.x = ....

def f(ns):   
    tmp = ns.x     # retrieve the shared value
    tmp.append(10)
    ns.x = tmp     # set the shared value
20
unutbu