Para mim, isso é bem simples:
A opção de subprocesso :
subprocess
é para executar outros executáveis --- é basicamente um wrapper os.fork()
e os.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 importar fcntl
e manipular seus Popen
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 o signal
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 seu multiprocessing
IPC em torno dos Queue
objetos do módulo onde possível, mas você também pode usar Event
objetos e vários outros recursos (alguns dos quais são, presumivelmente, criados em torno do mmap
suporte nas plataformas onde esse suporte é suficiente).
O multiprocessing
módulo do Python se destina a fornecer interfaces e recursos que são muito semelhantes threading
, 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.
- (Observação: o uso de
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 para Event
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.