Gostaria de paralelizar meu programa Python para que ele possa usar vários processadores na máquina em que é executado. Minha paralelização é muito simples, em que todas as "threads" paralelas do programa são independentes e gravam sua saída em arquivos separados. Não preciso dos threads para trocar informações, mas é fundamental que eu saiba quando os threads terminam, pois algumas etapas do meu pipeline dependem de sua saída.
A portabilidade é importante, pois gostaria que fosse executado em qualquer versão do Python no Mac, Linux e Windows. Dadas essas restrições, qual é o módulo Python mais apropriado para implementar isso? Estou tentando decidir entre thread, subprocessamento e multiprocessamento, que parecem fornecer funcionalidades relacionadas.
Alguma opinião sobre isso? Eu gostaria da solução mais simples e portátil.
fonte
Respostas:
multiprocessing
é um ótimo tipo de módulo de canivete suíço. É mais geral do que threads, pois você pode até mesmo realizar cálculos remotos. Este é, portanto, o módulo que eu sugiro que você use.O
subprocess
módulo também permite que você inicie vários processos, mas achei menos conveniente de usar do que o novo módulo de multiprocessamento.Threads são notoriamente sutis e, com CPython, muitas vezes você está limitado a um núcleo, com eles (embora, como observado em um dos comentários, o Global Interpreter Lock (GIL) possa ser lançado em código C chamado de código Python) .
Eu acredito que a maioria das funções dos três módulos que você cita podem ser usadas de forma independente da plataforma. No lado da portabilidade, observe que
multiprocessing
só vem como padrão desde o Python 2.6 (no entanto, existe uma versão para algumas versões mais antigas do Python). Mas é um ótimo módulo!fonte
Para mim, isso é bem simples:
A opção de subprocesso :
subprocess
é para executar outros executáveis --- é basicamente um wrapperos.fork()
eos.execve()
com algum suporte para encanamento opcional (configuração de PIPEs de e para os subprocessos. Obviamente, você poderia outros mecanismos de comunicação entre processos (IPC), como soquetes, ou Posix ou Memória compartilhada SysV. Mas você ficará limitado a quaisquer interfaces e canais IPC suportados pelos programas que você está chamando.Normalmente, alguém usa qualquer um de forma
subprocess
síncrona --- simplesmente chamando algum utilitário externo e lendo sua saída ou aguardando sua conclusão (talvez lendo seus resultados de um arquivo temporário ou depois de publicá-los em algum banco de dados).No entanto, pode-se gerar centenas de subprocessos e pesquisá-los. Minha própria classe de utilitários favorita faz exatamente isso. A maior desvantagem do
subprocess
módulo é que o suporte de E / S geralmente bloqueia. Existe um rascunho do PEP-3145 para consertar isso em alguma versão futura do Python 3.x e um asyncproc alternativo (Aviso que leva direto ao download, não a qualquer tipo de documentação nem README). Eu também descobri que é relativamente fácil apenas importarfcntl
e manipular seusPopen
descritores de arquivo PIPE diretamente - embora eu não saiba se isso é portátil para plataformas não UNIX.(Atualização: 7 de agosto de 2019: suporte Python 3 para subprocessos ayncio : subprocessos asyncio )
subprocess
quase não tem suporte para manipulação de eventos ... embora você possa usar osignal
módulo e sinais simples do UNIX / Linux da velha escola --- matando seus processos suavemente, por assim dizer.A opção de multiprocessamento :
multiprocessing
é para executar funções dentro de seu código (Python) existente com suporte para comunicações mais flexíveis entre esta família de processos. Em particular, é melhor construir seumultiprocessing
IPC em torno dosQueue
objetos do módulo onde possível, mas você também pode usarEvent
objetos e vários outros recursos (alguns dos quais são, presumivelmente, criados em torno dommap
suporte nas plataformas onde esse suporte é suficiente).O
multiprocessing
módulo do Python se destina a fornecer interfaces e recursos que são muito semelhantesthreading
, permitindo ao CPython escalar seu processamento entre várias CPUs / núcleos, apesar do GIL (Global Interpreter Lock). Ele aproveita todo o bloqueio de SMP minucioso e esforço de coerência que foi feito pelos desenvolvedores do kernel do seu sistema operacional.A opção de threading :
threading
é para uma gama bastante estreita de aplicativos que são limitados por E / S (não precisam ser escalados em vários núcleos de CPU) e que se beneficiam da latência extremamente baixa e sobrecarga de comutação de troca de thread (com memória de núcleo compartilhada) vs. processo / mudança de contexto. No Linux, este é quase o conjunto vazio (os tempos de troca de processos do Linux são extremamente próximos aos de suas trocas de thread).threading
sofre de duas desvantagens principais em Python .Um, é claro, é específico da implementação --- afetando principalmente o CPython. Esse é o GIL. Para a maior parte, a maioria dos programas CPython não se beneficiará da disponibilidade de mais de duas CPUs (núcleos) e frequentemente o desempenho sofrerá com a contenção de bloqueio GIL.
O maior problema, que não é específico da implementação, é que os threads compartilham a mesma memória, manipuladores de sinal, descritores de arquivo e certos outros recursos do sistema operacional. Portanto, o programador deve ser extremamente cuidadoso com o bloqueio de objetos, tratamento de exceções e outros aspectos de seu código que são sutis e podem matar, paralisar ou bloquear todo o processo (conjunto de threads).
Em comparação, o
multiprocessing
modelo dá a cada processo sua própria memória, descritores de arquivo, etc. Uma falha ou exceção não tratada em qualquer um deles só matará esse recurso e lidar de forma robusta com o desaparecimento de um filho ou processo irmão pode ser consideravelmente mais fácil do que depurar, isolar e correção ou solução de problemas semelhantes em threads.threading
com os principais sistemas Python, como NumPy , pode sofrer consideravelmente menos com a contenção de GIL do que a maioria de seu próprio código Python. Isso porque eles foram especificamente projetados para isso; as partes nativas / binárias de NumPy, por exemplo, irá liberar o GIL quando for seguro).A opção distorcida :
Também é importante notar que Twisted oferece outra alternativa que é elegante e muito difícil de entender . Basicamente, correndo o risco de simplificar demais a ponto de os fãs do Twisted invadirem minha casa com forcados e tochas, o Twisted oferece multitarefa cooperativa orientada a eventos em qualquer processo (único).
Para entender como isso é possível, deve-se ler sobre os recursos do
select()
(que podem ser construídos em torno de select () ou poll () ou chamadas de sistema de sistema operacional semelhantes). Basicamente, é tudo impulsionado pela capacidade de fazer uma solicitação ao sistema operacional para hibernar enquanto se espera qualquer atividade em uma lista de descritores de arquivo ou algum tempo limite.O despertar de cada uma dessas chamadas para
select()
é um evento --- seja envolvendo entrada disponível (legível) em algum número de sockets ou descritores de arquivo, ou espaço de buffer tornando-se disponível em alguns outros descritores ou sockets (graváveis), algumas condições excepcionais (TCP pacotes PUSH fora da banda, por exemplo) ou um TIMEOUT.Assim, o modelo de programação Twisted é construído em torno do tratamento desses eventos, em seguida, em loop no manipulador "principal" resultante, permitindo que ele despache os eventos para seus manipuladores.
Pessoalmente, penso no nome Twisted como uma evocação do modelo de programação ... já que sua abordagem ao problema deve ser, em certo sentido, "torcida" de dentro para fora. Em vez de conceber seu programa como uma série de operações em dados de entrada e saídas ou resultados, você está escrevendo seu programa como um serviço ou daemon e definindo como ele reage a vários eventos. (Na verdade, o "loop principal" central de um programa Twisted é (normalmente? Sempre?) A
reactor()
).Os principais desafios de usar o Twisted envolvem torcer sua mente em torno do modelo orientado a eventos e também evitar o uso de quaisquer bibliotecas de classe ou kits de ferramentas que não foram escritos para cooperar dentro da estrutura Twisted. É por isso que Twisted fornece seus próprios módulos para manipulação de protocolo SSH, para curses, e seu próprio subprocesso / funções Popen, e muitos outros módulos e manipuladores de protocolo que, à primeira vista, parecem duplicar as coisas nas bibliotecas padrão do Python.
Acho que é útil entender o Twisted em um nível conceitual, mesmo que você nunca pretenda usá-lo. Ele pode fornecer insights sobre desempenho, contenção e manipulação de eventos em seu threading, multiprocessamento e até mesmo manipulação de subprocessos, bem como qualquer processamento distribuído que você empreenda.
( Nota: Novas versões do Python 3.x são incluindo asyncio (Asynchronous I / O) apresenta, como assíncrono def , o @ async.coroutine decorador, e aguardam palavra-chave, e rendimento de futuro apoio Todos estes são mais ou menos semelhante ao. Torcido de uma perspectiva de processo (multitarefa cooperativa). (Para obter o status atual do suporte Twisted para Python 3, verifique: https://twistedmatrix.com/documents/current/core/howto/python3.html )
A opção distribuída :
Ainda outro domínio de processamento que você não perguntou, mas que vale a pena considerar, é o de processamento distribuído . Existem muitas ferramentas e estruturas Python para processamento distribuído e computação paralela. Pessoalmente, acho que o mais fácil de usar é aquele que é considerado menos frequentemente naquele espaço.
É quase trivial criar processamento distribuído no Redis . Todo o armazenamento de chaves pode ser usado para armazenar unidades de trabalho e resultados, LISTs do Redis podem ser usados
Queue()
como objetos semelhantes e o suporte PUB / SUB pode ser usado paraEvent
manuseio semelhante. Você pode fazer o hash de suas chaves e valores de uso, replicados em um cluster frouxo de instâncias do Redis, para armazenar a topologia e os mapeamentos de token hash para fornecer hash consistente e failover para escalar além da capacidade de qualquer instância única para coordenar seus trabalhadores e dados de empacotamento (em conserva, JSON, BSON ou YAML) entre eles.É claro que, à medida que você começa a construir uma solução em maior escala e mais sofisticada em torno do Redis, você está reimplementando muitos dos recursos que já foram resolvidos usando Celery , Apache Spark e Hadoop , Zookeeper , etcd , Cassandra e assim por diante. Todos eles têm módulos para acesso Python a seus serviços.
[Atualização: Alguns recursos a serem considerados se você estiver considerando Python para uso intensivo de computação em sistemas distribuídos: IPython Parallel e PySpark . Embora sejam sistemas de computação distribuída de propósito geral, eles são particularmente acessíveis e populares para a ciência e análise de dados de subsistemas].
Conclusão
Lá você tem uma gama de alternativas de processamento para Python, de single threaded, com chamadas síncronas simples para subprocessos, pools de subprocessos pesquisados, threaded e multiprocessamento, multitarefa cooperativa orientada por evento e processamento externo para distribuído.
fonte
Em um caso semelhante, optei por processos separados e o pouco de comunicação necessária através de soquete de rede. É altamente portátil e bastante simples de fazer usando python, mas provavelmente não o mais simples (no meu caso, eu também tinha outra restrição: a comunicação com outros processos escritos em C ++).
No seu caso, eu provavelmente optaria por multiprocessos, já que threads de python, pelo menos ao usar CPython, não são threads reais. Bem, eles são threads nativos do sistema, mas os módulos C chamados de Python podem ou não liberar o GIL e permitir que outros threads sejam executados ao chamar o código de bloqueio.
fonte
Para usar vários processadores em CPython, sua única opção é o
multiprocessing
módulo. CPython mantém um bloqueio em seus internos (o GIL ), o que impede que threads em outros cpus funcionem em paralelo. Omultiprocessing
módulo cria novos processos (comosubprocess
) e gerencia a comunicação entre eles.fonte
Desembarque e deixe o unix fazer seu trabalho:
use iterpipes para envolver o subprocesso e, em seguida:
Do site de Ted Ziuba
INPUTS_FROM_YOU | xargs -n1 -0 -P NUM ./process #NUM processos paralelos
OU
Gnu Parallel também servirá
Você sai com GIL enquanto manda os meninos dos bastidores para fazer seu trabalho multicore.
fonte