Pergunta

Quais são os módulos usados ​​para escrever aplicativos multithread em Python?Estou ciente dos mecanismos básicos de simultaneidade fornecidos pela linguagem e também de Python sem pilha, mas quais são seus respectivos pontos fortes e fracos?

Foi útil?

Solução

Em ordem crescente de complexidade:

Use o módulo de rosqueamento

Prós:

  • É realmente fácil executar qualquer função (qualquer chamável de fato) em seu próprio thread.
  • Compartilhar dados, se não forem fáceis (os bloqueios nunca são fáceis :), pelo menos simples.

Contras:

  • Como mencionado por Juergen Threads Python não podem acessar simultaneamente o estado no interpretador (há um grande bloqueio, o infame Bloqueio global de intérprete.) O que isso significa na prática é que threads são úteis para tarefas vinculadas a E/S (rede, gravação em disco e assim por diante), mas nem um pouco úteis para fazer computação simultânea.

Use o multiprocessamento módulo

No caso de uso simples, isso se parece exatamente com o uso threading exceto que cada tarefa é executada em seu próprio processo e não em seu próprio thread.(Quase literalmente:Se você pegar O exemplo de Eli, e substitua threading com multiprocessing, Thread, com Process, e Queue (o módulo) com multiprocessing.Queue, ele deve funcionar perfeitamente.)

Prós:

  • Simultaneidade real para todas as tarefas (sem bloqueio global de intérprete).
  • Escala para vários processadores, pode até mesmo escalar para vários máquinas.

Contras:

  • Os processos são mais lentos que os threads.
  • O compartilhamento de dados entre processos é mais complicado do que com threads.
  • A memória não é compartilhada implicitamente.Você precisa compartilhá-lo explicitamente ou selecionar variáveis ​​​​e enviá-las de um lado para outro.Isso é mais seguro, mas mais difícil.(Se isso importa cada vez mais, os desenvolvedores de Python parecem estar empurrando as pessoas nessa direção.)

Use um modelo de evento, como Torcido

Prós:

  • Você obtém um controle extremamente preciso sobre a prioridade, sobre o que é executado e quando.

Contras:

  • Mesmo com uma boa biblioteca, a programação assíncrona é geralmente mais difícil do que a programação encadeada, difícil tanto em termos de compreensão do que deveria acontecer quanto em termos de depuração do que realmente está acontecendo.

Em todos Nestes casos, presumo que você já entenda muitos dos problemas envolvidos na multitarefa, especificamente a complicada questão de como compartilhar dados entre tarefas.Se por algum motivo você não sabe quando e como usar bloqueios e condições, você deve começar com eles.O código multitarefa é cheio de sutilezas e dicas, e é realmente melhor ter um bom entendimento dos conceitos antes de começar.

Outras dicas

Você já obteve uma boa variedade de respostas, desde "tópicos falsos" até estruturas externas, mas não vi ninguém mencionar Queue.Queue - o "molho secreto" do threading do CPython.

Expandir:contanto que você não precise sobrepor o processamento pesado da CPU do Python puro (nesse caso, você precisa multiprocessing - mas vem com seu próprio Queue implementação também, para que você possa, com alguns cuidados necessários, aplicar o conselho geral que estou dando ;-), o recurso integrado do Python threading vai fazer...mas será muito melhor se você usá-lo deliberadamente, por exemplo, como segue.

"Esqueça" a memória compartilhada, supostamente a principal vantagem do threading versus multiprocessamento - ela não funciona bem, não é bem dimensionada, nunca funcionou, nunca funcionará.Use memória compartilhada apenas para estruturas de dados configuradas uma vez antes você gera sub-threads e nunca mais muda depois - para todo o resto, faça um solteiro thread responsável por esse recurso e se comunicar com esse thread via Queue.

Dedique um thread especializado a cada recurso que você normalmente pensaria em proteger por bloqueios:uma estrutura de dados mutável ou grupo coeso dela, uma conexão com um processo externo (um banco de dados, um servidor XMLRPC, etc), um arquivo externo, etc, etc.Obtenha um pequeno pool de threads para tarefas de uso geral que não possuem ou precisam de um recurso dedicado desse tipo - não gerar threads como e quando necessário, ou a sobrecarga de troca de thread irá sobrecarregá-lo.

A comunicação entre dois threads é sempre via Queue.Queue - uma forma de passagem de mensagens, a única base sensata para multiprocessamento (além da memória transacional, que é promissora, mas para a qual não conheço nenhuma implementação digna de produção, exceto em Haskell).

Cada thread dedicado que gerencia um único recurso (ou um pequeno conjunto coeso de recursos) escuta solicitações em uma instância Queue.Queue específica.Threads em um pool esperam em um único Queue.Queue compartilhado (a fila é solidamente threadsafe e não vai falhar com você nisso).

Threads que precisam apenas enfileirar uma solicitação em alguma fila (compartilhada ou dedicada) fazem isso sem esperar pelos resultados e seguem em frente.Threads que eventualmente PRECISAM de um resultado ou confirmação para uma solicitação enfileiram um par (solicitação, fila de recebimento) com uma instância de Queue.Queue que acabaram de fazer e, eventualmente, quando a resposta ou confirmação for indispensável para prosseguir, eles obtêm (esperando ) da fila de recebimento.Certifique-se de estar pronto para receber respostas de erro, bem como respostas ou confirmações reais (Twisted's deferreds são ótimos para organizar esse tipo de resposta estruturada, aliás!).

Você também pode usar Queue para "estacionar" instâncias de recursos que podem ser usados ​​por qualquer thread, mas nunca podem ser compartilhados entre vários threads ao mesmo tempo (conexões de banco de dados com alguns componentes DBAPI, cursores com outros, etc.) - isso permite que você relaxe o requisito de thread dedicado em favor de mais pooling (um thread de pool que obtém da fila compartilhada uma solicitação que precisa de um recurso enfileirado obterá esse recurso da fila apropriada, aguardando se necessário, etc.).

Twisted é na verdade uma boa maneira de organizar este minueto (ou quadrilha, conforme o caso), não apenas graças aos diferidos, mas por causa de sua arquitetura base sólida, sólida e altamente escalável:você pode organizar as coisas para usar threads ou subprocessos somente quando realmente necessário, enquanto faz a maioria das coisas normalmente consideradas dignas de thread em um único thread orientado a eventos.

Mas, eu percebo que Twisted não é para todos - a abordagem "dedicar ou agrupar recursos, usar o Queue up the wazoo, nunca fazer nada que precise de um Lock ou, Guido me livre, qualquer procedimento de sincronização ainda mais avançado, como semáforo ou condição" pode ainda será usado mesmo que você simplesmente não consiga entender metodologias assíncronas orientadas a eventos e ainda oferecerá mais confiabilidade e desempenho do que qualquer outra abordagem de threading amplamente aplicável que eu já encontrei.

Depende do que você está tentando fazer, mas prefiro usar apenas o threading módulo na biblioteca padrão porque torna muito fácil pegar qualquer função e apenas executá-la em um thread separado.

from threading import Thread

def f():
    ...

def g(arg1, arg2, arg3=None):
    ....

Thread(target=f).start()
Thread(target=g, args=[5, 6], kwargs={"arg3": 12}).start()

E assim por diante.Muitas vezes tenho uma configuração de produtor/consumidor usando uma fila sincronizada fornecida pelo Queue módulo

from Queue import Queue
from threading import Thread

q = Queue()
def consumer():
    while True:
        print sum(q.get())

def producer(data_source):
    for line in data_source:
        q.put( map(int, line.split()) )

Thread(target=producer, args=[SOME_INPUT_FILE_OR_SOMETHING]).start()
for i in range(10):
    Thread(target=consumer).start()

Kamélia é uma estrutura python para construir aplicativos com muitos processos de comunicação.

(fonte: kamaelia.org) Kamaelia - Simultaneidade tornada útil e divertida

Na Kamaelia você constrói sistemas a partir de componentes simples que conversam entre si.Isso acelera o desenvolvimento, auxilia enormemente na manutenção e também significa que você construir software naturalmente simultâneo.Destina-se a ser acessível por qualquer desenvolvedor, incluindo novatos.Também torna tudo divertido :)

Que tipo de sistemas?Servidores de rede, clientes, aplicativos de desktop, jogos baseados em pygame, sistemas e pipelines de transcodificação, sistemas de TV digital, erradicadores de spam, ferramentas de ensino e muito mais :)

Aqui está um vídeo do Pycon 2009.Começa comparando Kamaelia com Torcido e Python Paralelo e então dá uma demonstração prática de Kamaelia.

Simultaneidade Fácil com Kamaelia - Parte 1 (59:08)
Simultaneidade Fácil com Kamaelia - Parte 2 (18:15)

Em relação a Kamaelia, a resposta acima não cobre realmente o benefício aqui.A abordagem de Kamaelia fornece uma interface unificada, que é pragmática e não perfeita, para lidar com threads, geradores e processos em um único sistema para simultaneidade.

Fundamentalmente, fornece uma metáfora de algo em execução que possui caixas de entrada e caixas de saída.Você envia mensagens para caixas de saída e, quando conectadas, as mensagens fluem das caixas de saída para as caixas de entrada.Esta metáfora/API permanece a mesma quer você esteja usando geradores, threads ou processos, ou falando com outros sistemas.

A parte "não perfeita" se deve ao fato de o açúcar sintático ainda não ter sido adicionado às caixas de entrada e de saída (embora isso esteja em discussão) - há um foco na segurança/usabilidade do sistema.

Tomando o exemplo do produtor consumidor usando threading simples acima, isso se torna isto em Kamaelia:

Pipeline(Producer(), Consumer() )

Neste exemplo, não importa se estes são componentes encadeados ou não, a única diferença entre eles do ponto de vista de uso é a classe base do componente.Os componentes do gerador se comunicam usando listas, componentes encadeados usando Queue.Queues e baseados em processos usando os.pipes.

A razão por trás dessa abordagem é dificultar a depuração de bugs.No threading - ou em qualquer simultaneidade de memória compartilhada que você tenha, o problema número um que você enfrenta são atualizações de dados compartilhadas acidentalmente quebradas.Ao usar a passagem de mensagens você elimina um classe de erros.

Se você usa threading e bloqueios simples em todos os lugares, geralmente está trabalhando na suposição de que, ao escrever código, não cometerá erros.Embora todos desejemos isso, é muito raro que isso aconteça.Ao agrupar o comportamento de bloqueio em um só lugar, você simplifica onde as coisas podem dar errado.(Os manipuladores de contexto ajudam, mas não ajudam com atualizações acidentais fora do manipulador de contexto)

Obviamente, nem todo pedaço de código pode ser escrito como passagem de mensagens e estilo compartilhado, e é por isso que Kamaelia também possui uma memória transacional de software simples (STM), que é uma ideia muito legal com um nome desagradável - é mais como controle de versão para variáveis ​​- ou seja verifique algumas variáveis, atualize-as e confirme novamente.Se você tiver um choque, enxágue e repita.

Links relevantes:

De qualquer forma, espero que seja uma resposta útil.FWIW, a principal razão por trás da configuração de Kamaelia é tornar a simultaneidade mais segura e fácil de usar em sistemas python, sem abanar o rabo.(ou seja, o grande balde de componentes

Posso entender por que a outra resposta de Kamaelia foi modificada, já que até para mim parece mais um anúncio do que uma resposta.Como autor de Kamaelia, é bom ver o entusiasmo, mas espero que contenha um conteúdo um pouco mais relevante :-)

E essa é a minha maneira de dizer, por favor, lembre-se de que esta resposta é, por definição, tendenciosa, mas para mim, o objetivo de Kamaelia é tentar encerrar o que é a melhor prática da IMO.Eu sugiro experimentar alguns sistemas e ver qual funciona para você.(também se isso for inapropriado para estouro de pilha, desculpe - sou novo neste fórum :-)

Eu usaria os Microthreads (Tasklets) do Stackless Python, se tivesse que usar threads.

Um jogo online completo (multijogador massivo) é construído em torno de Stackless e seu princípio multithreading - já que o original é apenas lento para a propriedade multijogador massivo do jogo.

Threads no CPython são amplamente desencorajados.Um dos motivos é o GIL – um bloqueio de intérprete global – que serializa o threading para muitas partes da execução.Minha experiência é que é realmente difícil criar aplicativos rápidos dessa maneira.Meus exemplos de codificação eram todos mais lentos com threading - com um núcleo (mas muitas esperas pela entrada deveriam ter possibilitado alguns aumentos de desempenho).

Com o CPython, use processos separados, se possível.

Se você realmente quer sujar as mãos, você pode tentar usando geradores para falsificar corrotinas.Provavelmente não é o mais eficiente em termos de trabalho envolvido, mas as corrotinas oferecem um controle muito preciso de cooperativo multitarefa em vez de multitarefa preventiva que você encontrará em outro lugar.

Uma vantagem que você encontrará é que, em geral, você não precisará de bloqueios ou mutexes ao usar multitarefa cooperativa, mas a vantagem mais importante para mim foi a velocidade de comutação quase zero entre "threads".É claro que Stackless Python também é muito bom para isso;e depois há Erlang, se não ter ser Python.

Provavelmente a maior desvantagem na multitarefa cooperativa é a falta geral de soluções alternativas para bloquear E/S.E nas corrotinas falsas, você também encontrará o problema de não poder alternar "threads" de nada além do nível superior da pilha dentro de um thread.

Depois de criar um aplicativo um pouco complexo com corrotinas falsas, você realmente começará a apreciar o trabalho envolvido no agendamento de processos no nível do sistema operacional.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top