No llamaría concurrent.futures
más "avanzado": es una interfaz más simple que funciona de manera muy similar independientemente de si usa múltiples hilos o múltiples procesos como el truco de paralelización subyacente.
Por lo tanto, como prácticamente todas las instancias de "interfaz más simple", están involucradas las mismas compensaciones: tiene una curva de aprendizaje más superficial, en gran parte solo porque hay mucho menos disponible para aprender; pero, debido a que ofrece menos opciones, eventualmente puede frustrarte de una manera que las interfaces más ricas no lo harán.
En lo que respecta a las tareas vinculadas a la CPU, eso es demasiado subespecificado para decir mucho significativo. Para las tareas vinculadas a la CPU en CPython, necesita múltiples procesos en lugar de múltiples subprocesos para tener alguna posibilidad de acelerar. Pero la cantidad (si la hay) de aceleración que obtiene depende de los detalles de su hardware, su sistema operativo y especialmente de la cantidad de comunicación entre procesos que requieren sus tareas específicas. Debajo de las cubiertas, todos los trucos de paralelización entre procesos dependen de las mismas primitivas del sistema operativo: la API de alto nivel que utiliza para obtener esas no es un factor principal en la velocidad de la línea de fondo.
Editar: ejemplo
Aquí está el código final que se muestra en el artículo al que hizo referencia, pero estoy agregando una declaración de importación necesaria para que funcione:
from concurrent.futures import ProcessPoolExecutor
def pool_factorizer_map(nums, nprocs):
# Let the executor divide the work among processes by using 'map'.
with ProcessPoolExecutor(max_workers=nprocs) as executor:
return {num:factors for num, factors in
zip(nums,
executor.map(factorize_naive, nums))}
Aquí está exactamente lo mismo usando en su multiprocessing
lugar:
import multiprocessing as mp
def mp_factorizer_map(nums, nprocs):
with mp.Pool(nprocs) as pool:
return {num:factors for num, factors in
zip(nums,
pool.map(factorize_naive, nums))}
Tenga en cuenta que la capacidad de usar multiprocessing.Pool
objetos como administradores de contexto se agregó en Python 3.3.
¿Con cuál es más fácil trabajar? LOL ;-) Son esencialmente idénticos.
Una diferencia es que Pool
admite tantas formas diferentes de hacer las cosas que es posible que no se dé cuenta de lo fácil que puede ser hasta que haya subido bastante en la curva de aprendizaje.
Nuevamente, todas esas formas diferentes son tanto una fortaleza como una debilidad. Son una fortaleza porque la flexibilidad puede ser necesaria en algunas situaciones. Son una debilidad debido a "preferiblemente solo una forma obvia de hacerlo". Un proyecto que se concurrent.futures
mantenga exclusivamente (si es posible) probablemente será más fácil de mantener a largo plazo, debido a la falta de novedad gratuita sobre cómo se puede usar su API mínima.
ProcessPoolExecutor
realidad tiene más opciones quePool
porqueProcessPoolExecutor.submit
devuelveFuture
instancias que permiten la cancelación (cancel
), verifica qué excepción se produjo (exception
) y agrega dinámicamente una devolución de llamada para que se invoque al finalizar (add_done_callback
). Ninguna de estas características está disponible conAsyncResult
instancias devueltas porPool.apply_async
. En otras formasPool
tiene más opciones debido ainitializer
/initargs
,maxtasksperchild
ycontext
enPool.__init__
, y más métodos expuestos porPool
ejemplo.Pool
era sobre los módulos.Pool
es una pequeña parte de lo que hay dentromultiprocessing
y está tan abajo en los documentos que la gente tarda un tiempo en darse cuenta de que incluso existemultiprocessing
. Esta respuesta particular se centró enPool
porque ese es todo el artículo al que se vinculó el OP, y quecf
es "mucho más fácil trabajar con él" simplemente no es cierto sobre lo que el artículo discutió. Más allá de eso,cf
'sas_completed()
también puede ser muy útil.