Condivisione di una coda di risultati tra diversi processi


92

La documentazione del multiprocessingmodulo mostra come passare una coda a un processo avviato con multiprocessing.Process. Ma come posso condividere una coda con processi di lavoro asincroni iniziati con apply_async? Non ho bisogno di join dinamici o altro, solo un modo per i lavoratori di riportare (ripetutamente) i loro risultati alla base.

import multiprocessing
def worker(name, que):
    que.put("%d is done" % name)

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=3)
    q = multiprocessing.Queue()
    workers = pool.apply_async(worker, (33, q))

Questo viene a mancare con: RuntimeError: Queue objects should only be shared between processes through inheritance. Capisco cosa significa, e capisco il consiglio di ereditare piuttosto che richiedere il decapaggio / deselezione (e tutte le restrizioni speciali di Windows). Ma come faccio a passare la coda in un modo che funzioni? Non riesco a trovare un esempio e ho provato diverse alternative che hanno fallito in vari modi. Aiuto per favore?

Risposte:


133

Prova a utilizzare multiprocessing.Manager per gestire la tua coda e anche per renderla accessibile a diversi lavoratori.

import multiprocessing
def worker(name, que):
    que.put("%d is done" % name)

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=3)
    m = multiprocessing.Manager()
    q = m.Queue()
    workers = pool.apply_async(worker, (33, q))

È stato così, grazie! Si è verificato un problema non correlato con la chiamata asincrona nel mio codice originale, quindi ho copiato anche la correzione nella tua risposta.
alexis

16
Qualche spiegazione perché queue.Queue()non è adatta a questo?
mrgloom

@mrgloom: è queue.Queuestato creato per il threading, utilizzando blocchi in memoria. In un ambiente multiprocesso, ogni sottoprocesso otterrebbe la propria copia di queue.Queue()un'istanza nel proprio spazio di memoria, poiché i sottoprocessi non condividono la memoria (principalmente).
LeoRochael

@alexis Come ottenere gli elementi da Manager (). Queue () dopo che più lavoratori hanno inserito dati in esso?
MSS

10

multiprocessing.Poolha già una coda dei risultati condivisa, non è necessario coinvolgere ulteriormente un file Manager.Queue. Manager.Queueè una queue.Queue(coda multithreading) nascosta, situata su un processo server separato ed esposta tramite proxy. Ciò aggiunge un sovraccarico aggiuntivo rispetto alla coda interna del pool. Contrariamente a quanto ci si affida alla gestione dei risultati nativa di Pool, Manager.Queuenon è garantito che i risultati vengano ordinati anche in.

I processi di lavoro non vengono avviati .apply_async(), questo accade già quando si crea un'istanza Pool. Ciò che è iniziato quando si chiama pool.apply_async()è un nuovo "lavoro". I processi di lavoro di Pool eseguono la funzione multiprocessing.pool.workersotto il cofano. Questa funzione si occupa di elaborare nuove "attività" trasferite all'interno del Pool Pool._inqueuee di rinviare i risultati al genitore tramite Pool._outqueue. Il tuo specificato funcverrà eseguito entro multiprocessing.pool.worker. funcha solo returnqualcosa e il risultato verrà automaticamente rimandato al genitore.

.apply_async() immediatamente (in modo asincrono) restituisce un AsyncResultoggetto (alias per ApplyResult). Devi chiamare .get()(sta bloccando) su quell'oggetto per ricevere il risultato effettivo. Un'altra opzione sarebbe registrare una funzione di callback , che viene attivata non appena il risultato è pronto.

from multiprocessing import Pool

def busy_foo(i):
    """Dummy function simulating cpu-bound work."""
    for _ in range(int(10e6)):  # do stuff
        pass
    return i

if __name__ == '__main__':

    with Pool(4) as pool:
        print(pool._outqueue)  # DEMO
        results = [pool.apply_async(busy_foo, (i,)) for i in range(10)]
        # `.apply_async()` immediately returns AsyncResult (ApplyResult) object
        print(results[0])  # DEMO
        results = [res.get() for res in results]
        print(f'result: {results}')       

Output di esempio:

<multiprocessing.queues.SimpleQueue object at 0x7fa124fd67f0>
<multiprocessing.pool.ApplyResult object at 0x7fa12586da20>
result: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Nota: la specifica del timeoutparametro -per .get()non interromperà l'effettiva elaborazione dell'attività all'interno del lavoratore, ma sblocca solo il genitore in attesa sollevando un multiprocessing.TimeoutError.


Interessante, lo proverò la prima volta che ho. Certamente non ha funzionato in questo modo nel 2012.
alexis

@alexis Python 2.7 (2010) qui manca solo il gestore di contesto e il error_callbackparametro -per apply_async, quindi non è cambiato molto da allora.
Darkonaut
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.