Compartir una cola de resultados entre varios procesos

92

La documentación del multiprocessingmódulo muestra cómo pasar una cola a un proceso con el que se inició multiprocessing.Process. Pero, ¿cómo puedo compartir una cola con los procesos de trabajo asincrónicos con los que se inició apply_async? No necesito una unión dinámica ni nada más, solo una forma para que los trabajadores informen (repetidamente) de sus resultados a la 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))

Esta falla con: RuntimeError: Queue objects should only be shared between processes through inheritance. Entiendo lo que esto significa y entiendo el consejo de heredar en lugar de requerir decapado / despegado (y todas las restricciones especiales de Windows). Pero, ¿cómo no me pase la cola de manera que las obras? No puedo encontrar un ejemplo y probé varias alternativas que fallaron de varias maneras. ¿Ayuda por favor?

alexis
fuente

Respuestas:

133

Intente usar multiprocessing.Manager para administrar su cola y también hacerla accesible para diferentes trabajadores.

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))
enderskill
fuente
Eso fue todo, gracias! Hubo un problema no relacionado con la llamada asíncrona en mi código original, así que también copié la solución a tu respuesta.
Alexis
16
¿Alguna explicación de por qué queue.Queue()no es adecuada para esto?
mrgloom
@mrgloom: queue.Queuefue construido para subprocesos, usando bloqueos en memoria. En un entorno multiproceso, cada subproceso obtendría su propia copia de una queue.Queue()instancia en su propio espacio de memoria, ya que los subprocesos no comparten memoria (en su mayoría).
LeoRochael
@alexis ¿Cómo obtener los elementos del Manager (). Queue () después de que varios trabajadores hayan insertado datos en él?
MSS
10

multiprocessing.Poolya tiene una cola de resultados compartida, no es necesario involucrar adicionalmente a Manager.Queue. Manager.Queuees una queue.Queue(cola de subprocesos múltiples) debajo del capó, ubicada en un proceso de servidor separado y expuesta a través de proxies. Esto agrega una sobrecarga adicional en comparación con la cola interna de Pool. Contrariamente a confiar en el manejo de resultados nativo de Pool, Manager.Queueno se garantiza que los resultados en el también estén ordenados.

Los procesos de trabajo no se inician con .apply_async(), esto ya sucede cuando crea una instancia Pool. Lo que se inicia cuando llamas pool.apply_async()es un nuevo "trabajo". Los procesos de trabajo de Pool ejecutan la multiprocessing.pool.workerfunción bajo el capó. Esta función se encarga de procesar las nuevas "tareas" transferidas a través del interno de Pool Pool._inqueuey de enviar los resultados al padre a través del Pool._outqueue. Su especificado funcse ejecutará dentro multiprocessing.pool.worker. funcsolo tiene que hacer returnalgo y el resultado se enviará automáticamente al padre.

.apply_async() Inmediatamente (asincrónicamente) devuelve un AsyncResultobjeto (alias de ApplyResult). Necesita llamar .get()(está bloqueando) en ese objeto para recibir el resultado real. Otra opción sería registrar una función de devolución de llamada , que se activa tan pronto como el resultado esté listo.

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}')       

Salida de ejemplo:

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

Nota: Especificar el timeoutparámetro -para .get()no detendrá el procesamiento real de la tarea dentro del trabajador, solo desbloquea al padre en espera al generar un multiprocessing.TimeoutError.

Darkonaut
fuente
Interesante, lo probaré a la primera oportunidad que tenga. Ciertamente no funcionó de esta manera en 2012.
alexis
@alexis Python 2.7 (2010) de manera relevante aquí solo falta el administrador de contexto y el error_callbackparámetro -para apply_async, por lo que no ha cambiado mucho desde entonces.
Darkonaut