Existem várias maneiras de executar com eficiência um loop de execução do Python em paralelo, dependendo do tipo de tarefa executada dentro do loop e dos recursos disponíveis. Aqui está um colapso de abordagens comuns e suas considerações:
1. Multiprocessamento (tarefas ligadas à CPU): -
Quando usar: Ideal para tarefas que são computacionalmente intensivas (ligadas à CPU), como trituração de números, processamento de imagens ou cálculos complexos. Essas tarefas se beneficiam mais da utilização de vários núcleos.
-
como funciona: Cria processos separados, cada um com seu próprio espaço de memória. Isso evita as limitações globais de bloqueio de intérprete (GIL), permitindo a verdadeira execução paralela.
-
Exemplo: `` `Python
importar multiprocessamento
tempo de importação
DEF Process_item (item):
"" "" Simula uma tarefa ligada à CPU. "" "
time.sleep (1) # simular trabalho
Item de devolução * 2
def main ():
itens =lista (intervalo (10))
start_time =time.time ()
com multiprocessing.pool (processos =multiprocessing.cpu_count ()) como pool:
Resultados =pool.map (process_item, itens)
end_time =time.time ()
print (F "Resultados:{Results}")
print (f "TEMPO TEMPO:{end_time - start_time:.2f} segundos")
Se __name__ =="__main__":
principal()
`` `
-
Explicação: - `multiprocessing.pool`:cria um conjunto de processos de trabalhador. `multiprocessing.cpu_count ()` usa automaticamente o número de núcleos disponíveis.
- `pool.map`:aplica a função` process_item` a cada item na lista `itens`, distribuindo o trabalho pelos processos do trabalhador. Ele lida automaticamente dividindo o trabalho e a coleta dos resultados.
- `pool.apply_async`:uma alternativa não bloqueadora ao` pool.map`. Você precisa coletar resultados usando `resultado.get ()` para cada item.
- `pool.iMap` e` pool.iMap_unordered`:iteradores que retornam os resultados à medida que ficam disponíveis. `IMAP_UNORDED` não garante a ordem dos resultados.
- `pool.starmap`:semelhante ao` pool.map`, mas permite passar vários argumentos para a função do trabalhador usando tuplas.
-
Vantagens: - supera a limitação do GIL para tarefas ligadas à CPU.
- usa vários núcleos de CPU com eficiência.
-
Desvantagens: - Despesas gerais mais altas do que rosquear devido à criação de processos separados.
- A comunicação entre os processos (passando dados) pode ser mais lenta.
- mais intensiva em memória, pois cada processo tem seu próprio espaço de memória.
- pode ser mais complexo para gerenciar o estado compartilhado (precisa de mecanismos de comunicação entre processos, como filas ou memória compartilhada).
2. Threading (tarefas ligadas a E/O): -
Quando usar: Adequado para tarefas que gastam uma quantidade significativa de tempo aguardando operações externas (ligadas à E/S), como solicitações de rede, leituras/gravações em disco ou consultas de banco de dados.
-
como funciona: Cria vários threads dentro de um único processo. Os tópicos compartilham o mesmo espaço de memória. O GIL (bloqueio global de intérprete) limita o paralelismo verdadeiro no cpython, mas os threads ainda podem melhorar o desempenho liberando o GIL ao aguardar a E/S.
-
Exemplo: `` `Python
importar rosqueamento
tempo de importação
def fetch_url (url):
"" "" Simula uma tarefa ligada a E/S. "" "
print (f "busca {url}")
time.sleep (2) # simule o atraso da rede
Imprima (F "FECTING FECTING {URL}")
Retorne F "Conteúdo de {url}"
def main ():
urls =["https://example.com/1", "https://example.com/2", "https://example.com/3"]
start_time =time.time ()
threads =[]
Resultados =[]
Para URL em URLs:
Thread =Threading.Thread (Target =Lambda u:Results.append (fetch_url (u)), args =(url,))
threads.append (thread)
Thread.start ()
Para threads em threads:
Thread.join () # Aguarde todos os threads concluírem
end_time =time.time ()
print (F "Resultados:{Results}")
print (f "TEMPO TEMPO:{end_time - start_time:.2f} segundos")
Se __name__ =="__main__":
principal()
`` `
-
Explicação: - `Threading.Thread`:cria um novo thread.
- `thread.start ()`:inicia a execução do thread.
- `thread.join ()`:aguarda o thread terminar.
-
Função lambda: Usado para passar o `url` como um argumento para` fetch_url` dentro do construtor `thread`. É essencial passar o `url` * por valor * para evitar condições de corrida, onde todos os threads podem acabar usando o último valor de` url '.
-
Vantagens: - Despesas inferiores do que multiprocessamento.
- compartilha o espaço da memória, facilitando o compartilhamento de dados entre os threads (mas requer sincronização cuidadosa).
- Pode melhorar o desempenho para tarefas ligadas a E/S, apesar do GIL.
-
Desvantagens: - O GIL limita o paralelismo verdadeiro para tarefas ligadas à CPU em Cpython.
- Requer sincronização cuidadosa (bloqueios, semáforos) para evitar condições de corrida e corrupção de dados ao acessar recursos compartilhados.
3. Asyncio (concorrência com um único thread): -
Quando usar: Excelente para lidar com um grande número de tarefas ligadas a E/S simultaneamente dentro de um único thread. Fornece uma maneira de escrever um código assíncrono que pode alternar entre tarefas enquanto aguarda a conclusão das operações de E/S.
-
como funciona: Usa um loop de eventos para gerenciar coroutinas (funções especiais declaradas com `assíncrona def`). As coroutinas podem suspender sua execução enquanto aguardam a E/S e permitem que outras coroutinas funcionem. `Asyncio` não * não fornece paralelismo verdadeiro (é simultaneamente), mas pode ser altamente eficiente para operações ligadas a E/S.
-
Exemplo: `` `Python
importar asyncio
tempo de importação
Async def fetch_url (url):
"" "" Simula uma tarefa ligada a E/S (assíncrona). "" "
print (f "busca {url}")
Aguarda asyncio.sleep (2) # simular o atraso da rede (não bloqueio)
Imprima (F "FECTING FECTING {URL}")
Retorne F "Conteúdo de {url}"
Async def main ():
urls =["https://example.com/1", "https://example.com/2", "https://example.com/3"]
start_time =time.time ()
Tarefas =[Fetch_url (URL) para URL em URLs]
Resultados =Aguarda asyncio.gather (*tarefas) # Execute tarefas simultaneamente
end_time =time.time ()
print (F "Resultados:{Results}")
print (f "TEMPO TEMPO:{end_time - start_time:.2f} segundos")
Se __name__ =="__main__":
asyncio.run (main ())
`` `
-
Explicação: - `assíncrono def`:define uma função assíncrona (coroutina).
- `Aguarda`:suspende a execução da coroutina até que a operação aguardada seja concluída. Ele libera controle para o loop do evento, permitindo que outras coroutinas sejam executadas.
- `syncio.sleep`:uma versão assíncrona do` time.sleep` que não bloqueia o loop do evento.
- `Asyncio.gather`:executa várias coroutinas simultaneamente e retorna uma lista de seus resultados na ordem em que foram enviados. `*Tasks` descompacta a lista de tarefas.
- `Asyncio.run`:inicia o loop de eventos Asyncio e executa a coroutina` main`.
-
Vantagens: - altamente eficiente para tarefas ligadas a E/S, mesmo com um único thread.
- Evita a sobrecarga de criar vários processos ou threads.
- Mais fácil de gerenciar a simultaneidade do que a rosqueamento (menos necessidade de bloqueios explícitos).
- Excelente para criar aplicativos de rede altamente escaláveis.
-
Desvantagens: - Requer o uso de bibliotecas e código assíncronos, que podem ser mais complexos para aprender e depurar do que o código síncrono.
- Não é adequado para tarefas ligadas à CPU (não fornece paralelismo verdadeiro).
- depende de bibliotecas compatíveis com assíncronas (por exemplo, `aiohttp` em vez de 'solicitações').
4. Concurrent.futures (abstração sobre multiprocessamento e rosqueamento): -
Quando usar: Fornece uma interface de alto nível para executar tarefas de forma assíncrona, usando threads ou processos. Permite alternar entre rosqueamento e multiprocessamento sem alterar significativamente seu código.
-
como funciona: Usa o `threadpoolExecutor` para encadeamento e` processpoolExecutor` para multiprocessamento.
-
Exemplo: `` `Python
importação concorrente.futures
tempo de importação
DEF Process_item (item):
"" "" Simula uma tarefa ligada à CPU. "" "
time.sleep (1) # simular trabalho
Item de devolução * 2
def main ():
itens =lista (intervalo (10))
start_time =time.time ()
com concurrent.futures.processpoolExecutor (max_workers =multiprocessing.cpu_count ()) como executor:
# Envie cada item ao executor
futuros =[Executor.submit (process_item, item) para item em itens]
# Espere que todos os futuros concluam e obtenha os resultados
Resultados =[FUTURO.RESULT () para o futuro em concorrente.futures.as_completed (Futures)]
end_time =time.time ()
print (F "Resultados:{Results}")
print (f "TEMPO TEMPO:{end_time - start_time:.2f} segundos")
Se __name__ =="__main__":
importar multiprocessamento
principal()
`` `
-
Explicação: - `Concurrent.futures.processpoolExecutor`:cria um conjunto de processos de trabalhadores. Você também pode usar `concorrente.futures.threadpoolExecutor` para threads.
- `Executor.subMit`:envia um chamável (função) ao executor para execução assíncrona. Retorna um objeto `futuro` representando o resultado da execução.
- `Concurrent.futures.as_completed`:um iterador que produz objetos` futuro` à medida que eles concluem, em nenhuma ordem específica.
- `futuro.Result ()`:Recupera o resultado da computação assíncrona. Ele bloqueará até que o resultado esteja disponível.
-
Vantagens: - Interface de alto nível, simplificando a programação assíncrona.
- Alterne facilmente entre encadeamentos e processos alterando o tipo de executor.
- Fornece uma maneira conveniente de gerenciar tarefas assíncronas e recuperar seus resultados.
-
Desvantagens: - pode ter um pouco mais de sobrecarga do que usar `multiprocessing` ou` shreading` diretamente.
Escolhendo a abordagem correta: | Abordagem | Tipo de tarefa | Limitação de Gil | Uso da memória | Complexidade |
| --------------------- | ----------------- | ---------------- | --------------- | ------------ |
| Multiprocessamento | Ligado à CPU | Superar | Alto | Moderado |
| Threading | E/O-Bound | Sim | Baixo | Moderado |
| ASYNCIO | E/O-Bound | Sim | Baixo | Alto |
| Concorrente.futures | Ambos | Depende | Varia | Baixo |
Considerações importantes: *
Tipo de tarefa (ligado à CPU vs. ligado a E/O): Este é o fator mais importante. As tarefas ligadas à CPU se beneficiam do multiprocessamento, enquanto as tarefas ligadas a E/O são mais adequadas para rosquear ou assíncel.
*
gil (bloqueio global de intérpretes): O GIL em Cpython limita o paralelismo verdadeiro no encadeamento. Se você precisar de um paralelismo verdadeiro para tarefas ligadas à CPU, use multiprocessamento.
*
Sobrecarga: O multiprocessamento tem uma sobrecarga mais alta que a rosqueamento e o Asyncio.
*
Uso da memória: O multiprocessamento usa mais memória porque cada processo tem seu próprio espaço de memória.
*
Complexidade: Asyncio pode ser mais complexo para aprender do que rosquear ou multiprocessamento.
*
compartilhamento de dados: O compartilhamento de dados entre processos (multiprocessamento) requer mecanismos de comunicação entre processos (filas, memória compartilhada), o que pode adicionar complexidade. Os threads compartilham o espaço da memória, mas requerem sincronização cuidadosa para evitar condições de corrida.
*
Suporte da biblioteca: Certifique -se de que as bibliotecas que você está usando sejam compatíveis com o Asyncio se você escolher essa abordagem. Muitas bibliotecas agora oferecem versões assíncronas (por exemplo, `aiohttp` para solicitações HTTP).
Melhores práticas: *
Profile seu código: Antes de implementar o paralelismo, perfure seu código para identificar os gargalos. Não otimize prematuramente.
*
Meça o desempenho: Teste diferentes abordagens e meça seu desempenho para determinar qual deles funciona melhor para o seu caso de uso específico.
*
Mantenha as tarefas independentes: Quanto mais independentes são suas tarefas, mais fácil será paralelizá -las.
*
manuseio exceções: Lidar adequadamente com exceções nas funções ou coroutinas do seu trabalhador para impedir que elas colidam com todo o aplicativo.
*
Use filas para comunicação: Se você precisar se comunicar entre processos ou threads, use filas para evitar condições de corrida e garantir a segurança da linha.
*
Considere uma fila de mensagem: Para sistemas complexos e distribuídos, considere usar uma fila de mensagens (por exemplo, RabbitMQ, Kafka) para processamento de tarefas assíncronas.
Ao considerar cuidadosamente esses fatores, você pode escolher a abordagem mais eficiente para executar o loop de execução do Python em paralelo e melhorar significativamente o desempenho do seu aplicativo. Lembre -se de testar e medir os resultados para garantir que sua abordagem escolhida esteja realmente fornecendo um benefício de desempenho.