Threading permite que partes diferentes do seu processo sejam executadas de forma concorrente (Fonte: RealPython). Essas partes diferentes geralmente são individuais e possuem uma unidade de execução separada pertencente ao mesmo processo. Um processo nada mais é do que um programa em execução que tem unidades individuais capazes de rodar em paralelo. Por exemplo, um navegador pode ser um processo; um aplicativo que executa várias câmeras simultaneamente pode ser um processo; um videogame é outro exemplo de processo.
Dentro de um processo, surge o conceito de múltiplas threads, popularmente conhecido como multi-threading, em que várias threads trabalham juntas para alcançar um objetivo comum. O maior benefício de usar threads é permitir que o programa seja executado em paralelo.
Vamos entender o conceito de threading com um exemplo. Imagine que você tem um aplicativo que conta o número de carros entrando e saindo do estacionamento de um shopping. Seu equipamento tem várias câmeras que monitoram a entrada e a saída, conectadas a um dispositivo central. Cada câmera terá um algoritmo para monitorar o fluxo de carros, todos pertencentes ao mesmo processo ou programa. Porém, cada câmera, junto com o algoritmo que roda sobre ela, pode fazer parte de uma thread separada. Mais ainda: a leitura dos frames da câmera e a inferência do algoritmo nesses frames também poderiam ser duas threads distintas.
Outro exemplo é um videogame em que o processo precisa executar tarefas em paralelo, como gráficos, interação do usuário e rede (em jogos multiplayer), já que ele precisa ser responsivo o tempo todo. Para isso, usa o conceito de multi-threading, no qual cada thread fica responsável por uma tarefa independente.
Uma thread tem seu próprio fluxo de execução, o que significa que o processo terá várias coisas acontecendo ao mesmo tempo.
É importante notar que todo processo tem pelo menos uma thread, chamada de main thread. Se o seu programa não define threads adicionais, ele terá pelo menos a thread principal, ou seja, o próprio processo.
Se você observar o diagrama acima, há um processo que contém a thread principal com variáveis globais e três threads diferentes: t1, t2 e t3. Cada thread tem suas variáveis locais e seu próprio fluxo de execução do código. As threads geralmente compartilham os recursos do processo: os recursos disponíveis para a thread principal também serão compartilhados entre t1, t2 e t3. Todas as threads terão acesso às variáveis globais, mantendo suas variáveis locais.
Existem diferentes tipos de threads:
Thread de kernel
Thread de usuário
Combinação de thread de kernel e de usuário
Agora, veja algumas vantagens de usar threading no seu programa:
O multi-threading pode acelerar a execução do programa, desde que haja múltiplas CPUs.
Ele também permite executar outras tarefas enquanto operações de I/O estão ocorrendo, usando várias threads ou até a thread principal junto com uma única thread. Por exemplo, a velocidade com que os frames da câmera são lidos e processados pelo algoritmo pode ser gerenciada por threads diferentes. Assim, o algoritmo não precisa esperar a leitura de um frame, e a leitura não precisa aguardar o fim da execução do algoritmo para ler o próximo frame.
Threads dentro do mesmo processo podem compartilhar a memória e os recursos da thread principal.
Desafios do threading
Lembre-se de que o Python, na implementação CPython, limita a execução a apenas uma thread por vez; portanto, threading pode não acelerar todas as tarefas. O motivo principal é o Global Interpreter Lock (GIL).
Se quiser aprender mais sobre o GIL, confira este tutorial.
Se o seu objetivo é acelerar tarefas intensivas de CPU, threading pode não ser a melhor solução. Nesses casos, o multi-processing costuma ser mais adequado.
Para executar um programa com várias threads, é necessário alternar rapidamente entre elas; por isso, o agendamento (scheduling) precisa ser bem planejado em programas com muitas threads.
O compartilhamento de recursos também pode ser um problema, já que todas as threads compartilham os mesmos recursos e a memória de variáveis globais. Assim, operações feitas em uma thread podem causar erro de memória em outra, ou outra thread pode não obter memória suficiente para executar sua tarefa.
Threading em Python
No Python, o módulo de threading é nativo, chama-se threading e pode ser importado diretamente.
Como quase tudo em Python é representado como objeto, threads também são objetos. Uma thread é capaz de:
Conter dados,
Ser armazenada em estruturas como dicionários, listas, sets etc.
Ser passada como parâmetro para uma função.
Uma thread também pode ser executada como um processo.
Uma thread em Python pode ter vários estados, como:
Wait (aguardando),
Locked (bloqueada).
Agora vamos aprender como implementar threading em Python.
Módulo thread no Python 3
Observe que o Python 3 mantém compatibilidade com o módulo thread, existente no Python 2.7. No Python 3, ele pode ser importado como módulo _thread. Vamos ver um exemplo para entender o módulo _thread.
import _thread #thread module importedimport time #time moduleTecnologia
Vamos definir uma função chamada thread_delay, que recebe dois parâmetros de entrada: o nome da thread e o atraso. Dentro dessa função, você vai:
Definir um contador iniciado em zero,
Criar um laço while que roda três vezes,
Dentro do while, usar time.sleep() para adicionar atraso — útil para entender a execução da thread. O atraso é em segundos,
Incrementar o contador em 1,
Para verificar se a execução da thread está fluindo, imprimir o nome da thread e o horário em que a thread foi executada.
Agora, para adicionar a funcionalidade de thread à função acima — ou executá-la em uma thread — você usaria o método start_new_thread, que fica dentro do módulo _thread.
Vamos passar a função thread_delay sem parênteses e os dois argumentos — o nome da thread e o atraso (para visualizar a execução da thread, já que é muito rápida).
No output acima, você vê que a thread t1 começa a executar primeiro. Enquanto isso, a thread t2 aguarda, pois tem um atraso de 3 segundos; assim que o atraso termina, t2 é executada — lembrando que o atraso de t1 é de apenas 1 segundo.
Agora vamos alterar o atraso de t2 para 5 segundos, o que fará t2 iniciar depois que t1 terminar sua execução, já que t1 leva apenas 3 segundos para concluir.
Você também pode usar o método join, que faz a thread atual aguardar até que a execução das outras threads termine. Ou seja, qualquer código após join só será executado quando essas threads forem finalizadas.
t1.start()
t2.start()
t1.join()
t2.join()print("Thread execution is complete!")Tecnologia
Thread execution is complete!
Exception in thread Thread-9:Traceback(most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926,in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870,in run
self._target(*self._args,**self._kwargs)TypeError:volume_square() argument after * must be an iterable, not int
Exception in thread Thread-8:Traceback(most recent call last):
File "C:\Program Files\Anaconda3\lib\threading.py", line 926,in _bootstrap_inner
self.run()
File "C:\Program Files\Anaconda3\lib\threading.py", line 870,in run
self._target(*self._args,**self._kwargs)TypeError:volume_cube() argument after * must be an iterable, not int
Tecnologia
Ué, como assim? Ao executar o método start, ocorreu um erro. Isso acontece porque o parâmetro args espera uma tupla e, como volume_cube e volume_square esperam apenas um parâmetro, você precisa colocar uma vírgula no args após especificar o primeiro argumento das funções.
t1.start()
t2.start()
t1.join()
t2.join()print("Thread execution is complete!")Tecnologia
Volume ofCube:8
Volume ofSquare:9
Thread execution is complete!Tecnologia
Agora vamos aprender a usar threading como subclasse.
Threading como subclasse
Nesta seção, você vai aprender a criar uma subclasse a partir da classe Thread, disponível no módulo threading, e então criar as instâncias t1 e t2 dessa classe.
Vamos reutilizar a função thread_delay aqui também.
Você definiu uma classe DataCampThread e, como argumento de classe, passou threading.Thread. A razão é criar uma subclasse do módulo threading. Assim, você pode usar todos os métodos disponíveis em threading.Thread, de forma semelhante à herança.
Em seguida, você definiu o método init da classe DataCampThread e passou o nome e o delay. O init é um construtor; semelhante aos parâmetros que você passaria para threading.Thread, aqui você passa para DataCampThread.
Depois, você inicializa o init da classe base threading.Thread e atribui name e delay.
Na sequência, define o método run. Lembre que aqui você chamou o método start, que por baixo dos panos chama run. Como você herdou a classe principal, pode alterar o método run. Assim, quando chamar start, as modificações em run serão aplicadas.
Conclusão
Parabéns por concluir o tutorial.
Este foi uma introdução básica ao threading em Python. No entanto, threading é um tema amplo e essencial, e alguns conceitos podem ter ficado de fora. Sinta-se à vontade para explorar outros métodos, como active_count, current_thread, etc.
Este guia completo ensina os fundamentos do desenvolvimento backend com Python. Aprenda conceitos básicos, frameworks e boas práticas para começar a criar aplicações web.
Um tutorial introdutório completo sobre loops em Python. Aprenda e pratique loops while e for, loops aninhados, as palavras-chave break e continue, a função range e muito mais!
Mergulhe na ciência de dados com Python para analisar e visualizar seus dados de forma eficaz. Não precisa ter experiência ou conhecimento em programação.
Se você está começando em Python e quer aprender mais, faça o curso Introduction to Data Science in Python da DataCamp.
Introdução
Threading permite que partes diferentes do seu processo sejam executadas de forma concorrente (Fonte: RealPython). Essas partes diferentes geralmente são individuais e possuem uma unidade de execução separada pertencente ao mesmo processo. Um processo nada mais é do que um programa em execução que tem unidades individuais capazes de rodar em paralelo. Por exemplo, um navegador pode ser um processo; um aplicativo que executa várias câmeras simultaneamente pode ser um processo; um videogame é outro exemplo de processo.
Dentro de um processo, surge o conceito de múltiplas threads, popularmente conhecido como multi-threading, em que várias threads trabalham juntas para alcançar um objetivo comum. O maior benefício de usar threads é permitir que o programa seja executado em paralelo.
Vamos entender o conceito de threading com um exemplo. Imagine que você tem um aplicativo que conta o número de carros entrando e saindo do estacionamento de um shopping. Seu equipamento tem várias câmeras que monitoram a entrada e a saída, conectadas a um dispositivo central. Cada câmera terá um algoritmo para monitorar o fluxo de carros, todos pertencentes ao mesmo processo ou programa. Porém, cada câmera, junto com o algoritmo que roda sobre ela, pode fazer parte de uma thread separada. Mais ainda: a leitura dos frames da câmera e a inferência do algoritmo nesses frames também poderiam ser duas threads distintas.
Outro exemplo é um videogame em que o processo precisa executar tarefas em paralelo, como gráficos, interação do usuário e rede (em jogos multiplayer), já que ele precisa ser responsivo o tempo todo. Para isso, usa o conceito de multi-threading, no qual cada thread fica responsável por uma tarefa independente.
Uma thread tem seu próprio fluxo de execução, o que significa que o processo terá várias coisas acontecendo ao mesmo tempo.
É importante notar que todo processo tem pelo menos uma thread, chamada de
main thread. Se o seu programa não define threads adicionais, ele terá pelo menos a thread principal, ou seja, o próprio processo.Se você observar o diagrama acima, há um processo que contém a thread principal com variáveis globais e três threads diferentes: t1, t2 e t3. Cada thread tem suas variáveis locais e seu próprio fluxo de execução do código. As threads geralmente compartilham os recursos do processo: os recursos disponíveis para a thread principal também serão compartilhados entre t1, t2 e t3. Todas as threads terão acesso às variáveis globais, mantendo suas variáveis locais.
Existem diferentes tipos de threads:
Agora, veja algumas vantagens de usar threading no seu programa:
O multi-threading pode acelerar a execução do programa, desde que haja múltiplas CPUs.
Ele também permite executar outras tarefas enquanto operações de I/O estão ocorrendo, usando várias threads ou até a thread principal junto com uma única thread. Por exemplo, a velocidade com que os frames da câmera são lidos e processados pelo algoritmo pode ser gerenciada por threads diferentes. Assim, o algoritmo não precisa esperar a leitura de um frame, e a leitura não precisa aguardar o fim da execução do algoritmo para ler o próximo frame.
Desafios do threading
Lembre-se de que o Python, na implementação CPython, limita a execução a apenas uma thread por vez; portanto, threading pode não acelerar todas as tarefas. O motivo principal é o Global Interpreter Lock (GIL).
Se quiser aprender mais sobre o GIL, confira este tutorial.
Se o seu objetivo é acelerar tarefas intensivas de CPU, threading pode não ser a melhor solução. Nesses casos, o multi-processing costuma ser mais adequado.
Para executar um programa com várias threads, é necessário alternar rapidamente entre elas; por isso, o agendamento (scheduling) precisa ser bem planejado em programas com muitas threads.
O compartilhamento de recursos também pode ser um problema, já que todas as threads compartilham os mesmos recursos e a memória de variáveis globais. Assim, operações feitas em uma thread podem causar erro de memória em outra, ou outra thread pode não obter memória suficiente para executar sua tarefa.
Threading em Python
No Python, o módulo de threading é nativo, chama-se
threadinge pode ser importado diretamente.Como quase tudo em Python é representado como objeto, threads também são objetos. Uma thread é capaz de:
Uma thread também pode ser executada como um processo.
Uma thread em Python pode ter vários estados, como:
Agora vamos aprender como implementar threading em Python.
Módulo thread no Python 3
Observe que o Python 3 mantém compatibilidade com o módulo
thread, existente no Python 2.7. No Python 3, ele pode ser importado como módulo_thread. Vamos ver um exemplo para entender o módulo_thread.Vamos definir uma função chamada
thread_delay, que recebe dois parâmetros de entrada: o nome da thread e o atraso. Dentro dessa função, você vai:time.sleep()para adicionar atraso — útil para entender a execução da thread. O atraso é em segundos,Agora, para adicionar a funcionalidade de thread à função acima — ou executá-la em uma thread — você usaria o método
start_new_thread, que fica dentro do módulo_thread.Vamos ver a docstring do método
start_new_thread.Vamos passar a função
thread_delaysem parênteses e os dois argumentos — o nome da thread e o atraso (para visualizar a execução da thread, já que é muito rápida).No output acima, você vê que a thread
t1começa a executar primeiro. Enquanto isso, a threadt2aguarda, pois tem um atraso de 3 segundos; assim que o atraso termina,t2é executada — lembrando que o atraso det1é de apenas 1 segundo.Agora vamos alterar o atraso de
t2para 5 segundos, o que farát2iniciar depois quet1terminar sua execução, já quet1leva apenas 3 segundos para concluir.Implementando threading com o módulo threading
Vamos usar o mesmo exemplo acima, mas agora com o módulo
threadingem vez de_thread.No módulo
threadinghá uma classeThread, em espírito semelhante à funçãostart_new_threaddo módulo_thread.Vamos ver a docstring da classe
Thread, que recebe vários parâmetros como group, target (uma função), args etc.No construtor da classe Thread, você passa a função alvo
thread_delaye os argumentos dessa função.No módulo threading, para executar a thread você usa o método
start(), responsável por iniciá-la.Você também pode usar o método
join, que faz a thread atual aguardar até que a execução das outras threads termine. Ou seja, qualquer código apósjoinsó será executado quando essas threads forem finalizadas.Agora vamos calcular o volume de um cubo e de um quadrado usando threading — é bem direto.
Ué, como assim? Ao executar o método start, ocorreu um erro. Isso acontece porque o parâmetro
argsespera uma tupla e, comovolume_cubeevolume_squareesperam apenas um parâmetro, você precisa colocar uma vírgula noargsapós especificar o primeiro argumento das funções.Agora vamos aprender a usar threading como subclasse.
Threading como subclasse
Nesta seção, você vai aprender a criar uma subclasse a partir da classe Thread, disponível no módulo threading, e então criar as instâncias
t1et2dessa classe.Vamos reutilizar a função
thread_delayaqui também.Entenda o código acima, passo a passo:
Você definiu uma classe
DataCampThreade, como argumento de classe, passouthreading.Thread. A razão é criar uma subclasse do módulo threading. Assim, você pode usar todos os métodos disponíveis emthreading.Thread, de forma semelhante à herança.Em seguida, você definiu o método init da classe DataCampThread e passou o nome e o delay. O init é um construtor; semelhante aos parâmetros que você passaria para
threading.Thread, aqui você passa paraDataCampThread.Depois, você inicializa o init da classe base threading.Thread e atribui name e delay.
Na sequência, define o método
run. Lembre que aqui você chamou o métodostart, que por baixo dos panos chamarun. Como você herdou a classe principal, pode alterar o métodorun. Assim, quando chamarstart, as modificações emrunserão aplicadas.Conclusão
Parabéns por concluir o tutorial.
Este foi uma introdução básica ao threading em Python. No entanto, threading é um tema amplo e essencial, e alguns conceitos podem ter ficado de fora. Sinta-se à vontade para explorar outros métodos, como active_count, current_thread, etc.
Se quiser aprender mais, confira também a documentação de Thread-based Parallelism do Python.
Se você está começando em Python e quer aprender mais, faça o curso Introduction to Data Science in Python da DataCamp.