Multithreading em Python [com exemplos de codificação]
Publicados: 2020-11-30Melhorar e tornar o código mais rápido é o próximo passo após o conhecimento básico de Python ser adquirido. O multithreading é uma dessas maneiras de alcançar essa otimização usando “Threads”. Quais são esses fios? E como eles são diferentes dos processos? Vamos descobrir.
Ao final deste tutorial, você terá o conhecimento do seguinte:
- O que são threads e processos?
- Como o Multithreading é alcançado?
- Quais são suas limitações?
- Quando usar Multithreading?
Aprenda cursos online de ciência de dados das melhores universidades do mundo. Ganhe Programas PG Executivos, Programas de Certificado Avançado ou Programas de Mestrado para acelerar sua carreira.
Índice
Tópicos em Python
Quando pensamos em multitarefa, pensamos em execução paralela. Execução multithread não estritamente paralela. Threads podem ser consideradas como entidades separadas de fluxo de execução de diferentes partes do seu programa rodando independentemente. Então, essencialmente, os threads não são executados em paralelo, mas o Python alterna de um thread para outro tão rápido que parece que eles são paralelos.
Os processos, por outro lado, são estritamente paralelos e executados em diferentes núcleos para obter uma execução mais rápida. Threads também podem ser executados em diferentes processadores, mas eles ainda não serão executados em paralelo tecnicamente.
E você está pensando se os threads não são executados em paralelo, então como eles tornam as coisas mais rápidas? A resposta é que eles nem sempre tornam o processamento mais rápido. O multithreading é usado especificamente em tarefas em que os threads tornarão o processamento mais rápido.
Todas as informações de uma thread estão contidas no Thread Control Block(TCB) . O TCB consiste nas seguintes partes principais:
- Um TID exclusivo - Identificador de Thread
- Ponteiro de pilha que aponta para a pilha do thread no processo
- Um contador de programa que armazena o endereço da instrução que está sendo executada pela thread
- Estado do Thread (em execução, pronto, aguardando, iniciar ou concluído)
Dito isto, os processos podem conter vários threads que compartilham o código, os dados e todos os arquivos. E todos os encadeamentos têm seu próprio registro e pilha separados aos quais têm acesso.
Agora você pode se perguntar, se os encadeamentos usam os dados e o código comuns, como eles podem usá-los sem prejudicar outros encadeamentos. Esta é a maior limitação do Multithreading sobre o qual falaremos mais adiante neste tutorial.
Troca de contexto
Agora, como descrito acima, os threads não são executados paralelamente, mas consequentemente. Assim, quando uma thread T1 inicia a execução, todas as outras threads permanecem em modo de espera. Somente depois que T1 terminar sua execução, qualquer outro encadeamento enfileirado começará a ser executado. Python muda de um thread para outro tão rápido que parece uma execução paralela. Essa troca é o que chamamos de 'Mudança de Contexto'.
Programação multithread
Considere o código abaixo que usa threads para executar uma operação de cubo e quadrado.
importar rosqueamento def cubo (n) : print( “Cubo: {}” .format(n * n * n)) def quadrado (n) : print( “Quadrado: {}” .format(n * n)) if __name__ == “__main__” : #cria o tópico t1 = encadeamento.Thread(target=squarer, args=( 5 ,)) t2 = threading.Thread(target=cuber, args=( 5 ,)) # inicia a thread t1 t1.start() # inicia a thread t2 t2.start() # espera até que t1 seja concluído t1.join() # espera até que t2 seja concluído t2.join() # ambos os tópicos concluídos print( “Concluído!” ) |
#Saída: Quadrado: 25 Cubo: 125 Feito! |
Agora vamos tentar entender o código.
Primeiro, importamos o módulo Threading que é responsável por todas as tarefas. Dentro do main, criamos 2 threads criando subclasses da classe Thread. Precisamos passar o alvo, que é a função que precisa ser executada nessa thread, e os argumentos que precisam ser passados para essas funções.
Agora, uma vez que os threads são declarados, precisamos iniciá-los. Isso é feito chamando o método start em threads. Uma vez iniciado, o programa principal precisa esperar que as threads terminem seu processamento. Usamos o método wait para deixar o programa principal pausar e esperar que as threads T1 e T2 terminem sua execução.
Leitura obrigatória: Desafios do Python para iniciantes
Sincronização de thread
Como discutimos acima, os threads não são executados em paralelo, em vez disso, o Python alterna de um para outro. Portanto, há uma necessidade muito crítica de sincronização correta entre as threads para evitar qualquer comportamento estranho.
Condição de corrida
Threads que estão sob o mesmo processo usam dados e arquivos comuns que podem levar a uma “Race” para os dados entre vários threads. Portanto, se um dado for acessado por vários encadeamentos, ele será modificado por ambos os encadeamentos e os resultados que obteremos não serão os esperados. Isso é chamado de condição de corrida.
Portanto, se você tiver dois threads que tenham acesso aos mesmos dados, ambos poderão acessá-los e modificá-los quando esse thread específico estiver em execução. Assim, quando o T1 começa a executar e modifica alguns dados, o T2 está no modo de suspensão/espera. Em seguida, T1 interrompe a execução e entra em modo de suspensão passando o controle para T2, que também tem acesso aos mesmos dados. Portanto, o T2 agora modificará e substituirá os mesmos dados, o que levará a problemas quando o T1 for iniciado novamente.

O objetivo da Thread Synchronization é garantir que essa Race Condition nunca chegue e que a seção crítica do código seja acessada por threads uma de cada vez de maneira sincronizada.
Fechaduras
Para resolver e prevenir a Race Condition e suas consequências, o módulo thread oferece uma classe Lock que usa Semáforos para ajudar a sincronizar as threads. Os semáforos nada mais são do que sinalizadores binários. Considere-os como o sinal de “engajados” nas cabines telefônicas que tenham o valor de “engajados” (equivalente a 1) ou “não engajados” (equivalente a 0). Portanto, toda vez que um thread encontra um segmento de código com bloqueio, ele precisa verificar se o bloqueio já está em 1 estado. Se for, então terá que esperar até que se torne 0 para que possa usá-lo.
A classe Lock tem dois métodos principais:
- adquirir([bloqueio]) : O método de aquisição recebe o parâmetro de bloqueio como True ou False . Se um bloqueio para um thread T1 foi iniciado com bloqueio como True, ele aguardará ou permanecerá bloqueado até que a seção crítica do código seja bloqueada por outro thread T2. Uma vez que o outro thread T2 libera o bloqueio, o thread T1 adquire o bloqueio e retorna True .
Por outro lado, se o bloqueio da thread T1 foi iniciado com o bloqueio do parâmetro como False , a thread T1 não aguardará ou permanecerá bloqueada se a seção crítica já estiver bloqueada pela thread T2. Se o vir como bloqueado, ele retornará imediatamente False e sairá. No entanto, se o código não foi bloqueado por outro thread, ele adquirirá o bloqueio e retornará True .
release() : Quando o método de liberação é chamado no bloqueio, ele desbloqueará o bloqueio e retornará True. Além disso, ele verificará se algum thread está aguardando a liberação do bloqueio. Se houver, permitirá que exatamente um deles acesse o bloqueio.
No entanto, se o bloqueio já estiver desbloqueado, um ThreadError é gerado.
Impasses
Outra questão que surge quando lidamos com vários bloqueios é – Deadlocks. Os deadlocks ocorrem quando os bloqueios não são liberados por threads devido a vários motivos. Vamos considerar um exemplo simples onde fazemos o seguinte:
importar rosqueamento l = threading.Lock() # Antes da 1ª aquisição l.adquirir() # Antes da 2ª aquisição l.adquirir() # Agora adquiriu a fechadura duas vezes |
No código acima, chamamos o método de aquisição duas vezes, mas não o liberamos depois de adquirido pela primeira vez. Portanto, quando o Python vir a segunda instrução de aquisição, ele entrará no modo de espera indefinidamente, pois nunca liberamos o bloqueio anterior.
Essas condições de impasse podem se infiltrar em seu código sem que você perceba. Mesmo se você incluir uma chamada de liberação, seu código pode falhar no meio do caminho e a liberação nunca será chamada e a fechadura permanecerá bloqueada. Uma maneira de superar isso é usando a instrução with – as , também chamada de Gerenciadores de Contexto. Usando a instrução with – as , o bloqueio será liberado automaticamente assim que o processamento terminar ou falhar por qualquer motivo.
Leia: Ideias e tópicos do projeto Python
Antes de você ir
Como discutimos anteriormente, o Multithreading não é útil em todos os aplicativos, pois não faz com que as coisas sejam executadas em paralelo. Mas a principal aplicação do Multithreading é durante as tarefas de E/S, onde a CPU fica ociosa enquanto aguarda o carregamento dos dados. O multithreading desempenha um papel crucial aqui, pois esse tempo ocioso da CPU é utilizado em outras tarefas, tornando-o ideal para otimização.
Se você está curioso para aprender sobre ciência de dados, confira o Programa PG Executivo em Ciência de Dados do IIIT-B & upGrad, que é criado para profissionais que trabalham e oferece mais de 10 estudos de caso e projetos, workshops práticos práticos, orientação com especialistas do setor, 1 -on-1 com mentores do setor, mais de 400 horas de aprendizado e assistência de trabalho com as principais empresas.
O que é um thread em Python?
Threads são entidades dentro de um processo que podem ser agendadas para execução em Python. Em termos leigos, um thread é um processo de cálculo realizado por um computador. É um conjunto de tais instruções dentro de um programa que os desenvolvedores podem executar independentemente de outros scripts. Threads permitem aumentar a velocidade do aplicativo usando paralelismo. É um processo leve que permitirá que as tarefas operem em paralelo. Os threads operam independentemente e maximizam o uso da CPU, melhorando assim o desempenho da CPU.
Qual é o uso de multi-thread em Python?
Multithreading é uma técnica de threading na programação Python que permite que muitos threads operem simultaneamente, alternando rapidamente entre threads com a ajuda de uma CPU (chamada de alternância de contexto). Quando podemos dividir nossa tarefa em várias seções separadas, utilizamos multithreading. Por exemplo, suponha que você precise realizar uma consulta de banco de dados complexa para obter dados e dividir essa consulta em várias consultas individuais. Nesse caso, será preferível alocar uma thread para cada consulta e executá-las todas em paralelo.
O que é sincronização de threads?
A sincronização de threads é descrita como um método que garante que dois ou mais processos ou threads concorrentes não executem uma parte crucial de um programa simultaneamente. Os métodos de sincronização são usados para controlar o acesso de processos a seções importantes. Quando iniciamos duas ou mais threads dentro de um programa, há uma chance de que várias threads tentem acessar o mesmo recurso, resultando em resultados inesperados devido a desafios de simultaneidade. Por exemplo, se muitos encadeamentos tentarem gravar no mesmo arquivo, os dados poderão estar corrompidos porque um dos encadeamentos pode substituir dados ou quando um encadeamento está abrindo e outro está fechando o mesmo arquivo.