Eu não chamaria de concurrent.futures
mais "avançado" - é uma interface mais simples que funciona da mesma forma, independentemente de você usar vários threads ou vários processos como o truque de paralelização subjacente.
Portanto, como praticamente todas as instâncias de "interface mais simples", as mesmas vantagens e desvantagens estão envolvidas: ela possui uma curva de aprendizado mais rasa, em grande parte apenas porque há muito menos disponível para aprender; mas, como oferece menos opções, pode acabar frustrando você de maneiras que as interfaces mais ricas não o farão.
No que diz respeito às tarefas ligadas à CPU, isso é muito subespecificado para dizer muito significativo. Para tarefas ligadas à CPU no CPython, você precisa de vários processos, em vez de vários threads, para ter qualquer chance de obter uma aceleração. Mas quanto você recebe (se houver) de uma aceleração depende dos detalhes do seu hardware, sistema operacional e, principalmente, da quantidade de comunicação entre processos que suas tarefas específicas exigem. Nos bastidores, todos os truques de paralelismo entre processos dependem das mesmas primitivas do sistema operacional - a API de alto nível que você usa para obter essas informações não é o principal fator na velocidade da linha final.
Editar: exemplo
Aqui está o código final mostrado no artigo que você referenciou, mas estou adicionando uma declaração de importação necessária para fazê-la funcionar:
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))}
Aqui está exatamente a mesma coisa usando multiprocessing
:
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))}
Observe que a capacidade de usar multiprocessing.Pool
objetos como gerenciadores de contexto foi adicionada no Python 3.3.
Com quem é mais fácil trabalhar? LOL ;-) Eles são essencialmente idênticos.
Uma diferença é que Pool
suporta tantas maneiras diferentes de fazer as coisas que você talvez não perceba o quão fácil pode ser até que você tenha subido bastante a curva de aprendizado.
Novamente, todas essas maneiras diferentes são uma força e uma fraqueza. Eles são fortes porque a flexibilidade pode ser necessária em algumas situações. Eles são uma fraqueza por causa de "de preferência apenas uma maneira óbvia de fazê-lo". Um projeto que adere exclusivamente (se possível) concurrent.futures
provavelmente será mais fácil de manter a longo prazo, devido à falta de novidades gratuitas sobre como sua API mínima pode ser usada.
ProcessPoolExecutor
na verdade, tem mais opções do quePool
porqueProcessPoolExecutor.submit
retornaFuture
instâncias que permitem cancelamento (cancel
), verificando qual exceção foi gerada (exception
) e adicionando dinamicamente um retorno de chamada a ser chamado após a conclusão (add_done_callback
). Nenhum desses recursos está disponível comAsyncResult
instâncias retornadas porPool.apply_async
. Em outras formasPool
tem mais opções devido ainitializer
/initargs
,maxtasksperchild
econtext
naPool.__init__
, e mais métodos expostos porPool
exemplo.Pool
, era sobre os módulos.Pool
é uma pequena parte do conteúdomultiprocessing
e está tão distante nos documentos que leva um tempo até que as pessoas percebam que ele existemultiprocessing
. Essa resposta em particular foi focadaPool
porque esse é todo o artigo ao qual o OP se vinculou e quecf
é "muito mais fácil trabalhar" simplesmente não é verdade sobre o que o artigo discutiu. Além disso,cf
'sas_completed()
também pode ser muito útil.