2014-05-13 8 views
6

ho scritto un programma come questo:Multiprocessing di elenco condiviso

from multiprocessing import Process, Manager 

def worker(i): 
    x[i].append(i) 

if __name__ == '__main__': 
    manager = Manager() 
    x = manager.list() 
    for i in range(5): 
     x.append([]) 
    p = [] 
    for i in range(5): 
     p.append(Process(target=worker, args=(i,))) 
     p[i].start() 

    for i in range(5): 
     p[i].join() 

    print x 

Voglio creare un elenco condiviso di liste tra i processi e ogni processo modificare una lista in esso. Ma il risultato di questo programma è un elenco di liste vuote: [[], [], [], [], []].

Cosa non va?

+0

durante il debug di esso, è possibile impostare un punto di interruzione all'interno di lavoro def (i)? Questo potrebbe non essere chiamato – linpingta

+0

@linpingta Ho aggiunto una dichiarazione di stampa in worker (i) e il messaggio è stampato. –

+0

Sei sicuro di 'worker' che vuoi aggiungere a' x [i] 'e non a' x' (globale)? – beroe

risposta

6

Penso che questo sia dovuto a una stranezza nel modo in cui i manager vengono implementati.

Se si creano due oggetti Manager.list, e quindi aggiungere una delle liste per l'altro, il tipo di lista che si aggiunge cambiamenti all'interno della lista genitore:

>>> type(l) 
<class 'multiprocessing.managers.ListProxy'> 
>>> type(z) 
<class 'multiprocessing.managers.ListProxy'> 
>>> l.append(z) 
>>> type(l[0]) 
<class 'list'> # Not a ListProxy anymore 

l[0] e z non sono lo stesso oggetto, e non si comportano piuttosto il modo in cui ci si aspetta come risultato:

>>> l[0].append("hi") 
>>> print(z) 
[] 
>>> z.append("hi again") 
>>> print(l[0]) 
['hi again'] 

come si può vedere, la modifica della lista annidata non ha alcun effetto sull'oggetto ListProxy, ma cambiando la L'oggetto ListProxy cambia i nes ted list. La documentazione in realtà explicitly notes this:

Nota

Modifiche ai valori mutevoli o elementi in dict e lista proxy saranno non essere propagate tramite il gestore, in quanto la procura non ha modo di sapere quando i suoi valori o oggetti sono modificati. Per modificare tale elemento, è possibile ri-assegnare all'oggetto modificato al proxy contenitore:

Scavando attraverso il codice sorgente, si può vedere che quando si chiama append su un ListProxy, la chiamata di aggiunta viene effettivamente inviata a un oggetto gestore tramite IPC, quindi le chiamate del manager accodano all'elenco condiviso. Ciò significa che gli argomenti di append devono essere decapitati/non compilati. Durante il processo di unpickling, l'oggetto ListProxy viene trasformato in un normale elenco Python, che è una copia di ciò che ListProxy stava indicando (ovvero il suo referente). Questo è anche noted in the documentation:

Una caratteristica importante di oggetti proxy è che sono serializzabili così possono essere passati tra processi. Notare, tuttavia, che se un proxy viene inviato al processo del gestore corrispondente, quindi deselezionandolo, lo produce il referente stesso. Ciò significa, per esempio, che un oggetto condiviso può contenere una seconda

Quindi, tornando all'esempio precedente, se l [0] è una copia di z, perché l'aggiornamento z anche aggiornare l[0]? Poiché anche la copia viene registrata con l'oggetto Proxy, quindi, quando si modifica ListProxy (z nell'esempio sopra), vengono aggiornate anche tutte le copie registrate dell'elenco (l[0] nell'esempio sopra riportato). Tuttavia, la copia non sa nulla del proxy, quindi quando si cambia la copia, il Proxy non cambia.

Quindi, per fare in modo che il tuo esempio funzioni, devi creare un nuovo oggetto manager.list() ogni volta che vuoi modificare una sottolista e aggiornare solo quell'oggetto proxy direttamente, piuttosto che aggiornarlo tramite l'indice dell'elenco principale :

#!/usr/bin/python 

from multiprocessing import Process, Manager 

def worker(x, i, *args): 
    sub_l = manager.list(x[i]) 
    sub_l.append(i) 
    x[i] = sub_l 


if __name__ == '__main__': 
    manager = Manager() 
    x = manager.list([[]]*5) 
    print x 
    p = [] 
    for i in range(5): 
     p.append(Process(target=worker, args=(x, i))) 
     p[i].start() 

    for i in range(5): 
     p[i].join() 

    print x 

ecco l'output:

[email protected]:~$ ./multi_weirdness.py 
[[0], [1], [2], [3], [4]] 
+0

Grazie per la tua risposta! Intendevo cambiare sottolista tramite la lista dei genitori, ma sembra che non ci riesca. Io inizialmente uso la lista, ma rallenta la mia corsa parallela. C'è un altro modo per usare una lista condivisa che possa evitare questo problema? –

+0

Vedere l'esempio alla fine della mia risposta. Mostra come è possibile aggirare il problema creando un nuovo manager.list dalla sottolista, aggiornare il nuovo oggetto proxy e quindi reinserire il proxy nell'elenco padre. – dano

+0

Mi imbatto in un problema simile ma la soluzione alternativa non è un'opzione poiché la creazione di troppi proxy richiederebbe molto tempo. C'è un'altra soluzione? –