Introdução
Neste tutorial, aprenderemos como usar o módulo threading do Python para executar múltiplos threads de execução concorrentemente.
O módulo threading do Python fornece uma maneira simples de criar e gerenciar threads em um programa Python. Um thread é um fluxo de execução separado dentro de um programa. Ao executar múltiplos threads concorrentemente, podemos tirar proveito de CPUs multi-core e melhorar o desempenho de nossos programas.
O módulo threading fornece duas classes para criar e gerenciar threads:
- Classe
Thread: Esta classe representa um único thread de execução. - Classe
Lock: Esta classe permite sincronizar o acesso a recursos compartilhados entre threads.
Criando Threads
Para criar um novo thread em Python, precisamos criar uma nova instância da classe Thread e passar a ela uma função para executar.
Crie um projeto chamado create_thread.py no WebIDE e insira o seguinte conteúdo.
import threading
## Define a function to run in the thread
def my_function():
print("Hello from thread")
## Create a new thread
thread = threading.Thread(target=my_function)
## Start the thread
thread.start()
## Wait for the thread to finish
thread.join()
## Print "Done" to indicate that the program has finished
print("Done")
Este exemplo define uma função my_function que imprime uma mensagem. Em seguida, criamos uma nova instância da classe Thread, passando a ela my_function como a função alvo (target function). Finalmente, iniciamos o thread usando o método start e esperamos que ele termine usando o método join.
Use o seguinte comando para executar o script.
python create_thread.py
Sincronização
Se múltiplos threads acessam o mesmo recurso compartilhado (por exemplo, uma variável ou arquivo), devemos sincronizar o acesso a esse recurso para evitar condições de corrida (race conditions). O módulo threading do Python fornece uma classe Lock para essa finalidade.
Aqui está um exemplo de como usar um Lock para criar um projeto chamado sync.py no WebIDE e inserir o seguinte conteúdo.
import threading
## Create a lock to protect access to shared resource
lock = threading.Lock()
## Shared resource that will be modified by multiple threads
counter = 0
## Define a function that each thread will run
def my_function():
global counter
## Acquire the lock before accessing the shared resource
lock.acquire()
try:
## Access the shared resource
counter += 1
finally:
## Release the lock after modifying the shared resource
lock.release()
## Create multiple threads to access the shared resource
threads = []
## Create and start 10 threads that execute the same function
for i in range(10):
thread = threading.Thread(target=my_function)
threads.append(thread)
thread.start()
## Wait for all threads to complete their execution
for thread in threads:
thread.join()
## Print the final value of the counter
print(counter) ## Output: 10
Neste exemplo, criamos um objeto Lock e um recurso compartilhado counter. A função my_function acessa o recurso compartilhado adquirindo o lock usando o método acquire e liberando o lock usando o método release. Criamos múltiplos threads e os iniciamos, então esperamos que eles terminem usando o método join. Finalmente, imprimimos o valor final do contador.
Use o seguinte comando para executar o script.
python sync.py
Threads com Argumentos
Em Python, você pode passar argumentos para threads usando o parâmetro args ao criar um novo thread ou, alternativamente, criando uma subclasse da classe Thread e definindo seu construtor que aceita argumentos. Aqui estão exemplos de ambas as abordagens:
- Criamos uma subclasse da classe
Threade sobrescrevemos o métodorunpara definir o comportamento do thread. Crie um projeto chamadothread_subclass.pyno WebIDE e insira o seguinte conteúdo.
import threading
## Define a custom Thread class that extends the Thread class
class MyThread(threading.Thread):
## Override the run() method to implement thread's behavior
def run(self):
print("Hello from thread")
## Create an instance of the custom thread class
thread = MyThread()
## Start the thread
thread.start()
## Wait for the thread to finish execution
thread.join()
Use o seguinte comando para executar o script.
python thread_subclass.py
- Criamos um thread e passamos argumentos para a função alvo usando o parâmetro
args. Crie um projeto chamadothread_with_args.pyno WebIDE e insira o seguinte conteúdo
import threading
## Define a function that takes a parameter
def my_function(name):
print("Hello from", name)
## Create a new thread with target function and arguments
thread = threading.Thread(target=my_function, args=("Thread 1",))
## Start the thread
thread.start()
## Wait for the thread to finish execution
thread.join()
Use o seguinte comando para executar o script.
python thread_with_args.py
Thread Pool
Em Python, você pode usar um thread pool para executar tarefas concorrentemente usando um conjunto predefinido de threads. O benefício de usar um thread pool é que ele evita a sobrecarga de criar e destruir threads para cada tarefa, o que pode melhorar o desempenho.
O módulo concurrent.futures do Python fornece uma classe ThreadPoolExecutor que permite criar um pool de threads e submeter tarefas. Aqui está um exemplo:
Crie um projeto chamado thread_pool_range.py no WebIDE e insira o seguinte conteúdo.
import concurrent.futures
## Define a function to be executed in multiple threads with two arguments
def my_func(arg1, arg2):
## Define the tasks performed by the thread here
print(f"Hello from my thread with args {arg1} and {arg2}")
## Create a ThreadPoolExecutor object with a maximum of 5 worker threads
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
## Submit each task (function call with its arguments) to the executor for processing in a separate thread
## The submit() method returns a Future object representing the result of the asynchronous computation
for i in range(10):
executor.submit(my_func, i, i+1)
Neste exemplo, definimos uma função my_func que recebe dois argumentos. Criamos um ThreadPoolExecutor com um máximo de 5 threads de trabalho. Em seguida, iteramos por uma faixa de números e submetemos tarefas ao thread pool usando o método executor.submit(). Cada tarefa submetida é executada em um dos threads de trabalho disponíveis.
Dicas: O objeto
ThreadPoolExecutoré usado como um gerenciador de contexto. Isso garante que todos os threads sejam limpos adequadamente quando o código dentro do blocowithfor concluído.
Use o seguinte comando para executar o script.
python thread_pool_range.py
O método submit() retorna um objeto Future imediatamente, representando o resultado da tarefa submetida. Você pode usar o método result() do objeto Future para recuperar o valor de retorno da tarefa. Se a tarefa lançar uma exceção, chamar result() lançará essa exceção.
Você também pode usar o método map() da classe ThreadPoolExecutor para aplicar a mesma função a uma coleção de itens. Por exemplo, crie um projeto chamado thread_pool_map.py no WebIDE e insira o seguinte conteúdo:
import concurrent.futures
## Define a function to be executed in multiple threads
def my_func(item):
## Define the tasks performed by the thread here
print(f"Hello from my thread with arg {item}")
## Create a list of items to be processed by the threads
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
## Create a ThreadPoolExecutor object with a maximum of 5 worker threads
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
## Submit each item to the executor for processing in a separate thread
## The map() method automatically returns the results in order
executor.map(my_func, items)
Neste exemplo, definimos uma função my_func que recebe um argumento. Criamos uma lista de itens e os submetemos ao thread pool usando o método executor.map(). Cada item na lista é passado para my_func como um argumento, e cada item é executado em um dos threads de trabalho disponíveis.
Use o seguinte comando para executar o script.
python thread_pool_map.py
Os resultados obtidos de thread_pool_range.py e thread_pool_map.py são os mesmos.
Daemon Threads
Em Python, um daemon thread é um tipo de thread que é executado em segundo plano e não impede que o programa seja encerrado. Quando todos os threads não daemon foram concluídos, o interpretador Python é encerrado, independentemente de algum daemon thread ainda estar em execução.
Crie um projeto chamado daemon_thread_with_args.py no WebIDE e insira o seguinte conteúdo.
import threading
import time
## Define a function that runs indefinitely and prints messages at a regular interval
def my_function():
while True:
print("Hello from thread")
time.sleep(1)
## Create a daemon thread that runs the target function
thread = threading.Thread(target=my_function, daemon=True)
## Start the thread
thread.start()
## The main program continues to execute and prints a message
print("Main program")
## Wait for a few seconds before exiting the program
time.sleep(5)
## The program exits, and the daemon thread is terminated automatically
print("Main thread exiting...")
Neste exemplo, criamos um thread que executa um loop infinito e imprime uma mensagem a cada segundo usando a função time.sleep(). Marcamos o thread como um daemon usando o parâmetro daemon para sair quando o programa principal sair automaticamente. O programa principal continua a ser executado e imprime uma mensagem. Esperamos alguns segundos, e o programa sai, encerrando o daemon thread.
Em seguida, use o seguinte comando para executar o script.
python daemon_thread_with_args.py
Claro, também podemos definir um thread como um daemon chamando o método setDaemon(True) na instância do thread. Por exemplo, crie um projeto chamado daemon_thread_with_func.py no WebIDE e insira o seguinte conteúdo:
import threading
import time
## Define a function that runs indefinitely and prints messages at a regular interval
def my_function():
while True:
print("Hello from thread")
time.sleep(1)
## Create a new thread with target function
thread = threading.Thread(target=my_function)
## Set the daemon flag to True so that the thread runs in the background and terminates when the main program exits
thread.setDaemon(True)
## Start the thread
thread.start()
## The main program continues to run and prints a message
print("Main program")
## Wait for a few seconds before exiting the program
time.sleep(5)
## The program exits and the daemon thread is terminated automatically
print("Main thread exiting...")
A execução do script com o seguinte comando alcançará o mesmo resultado que o exemplo acima.
python daemon_thread_with_func.py
Objeto Evento
Em Python, você pode usar o objeto threading.Event para permitir que threads esperem que um evento específico ocorra antes de prosseguir. O objeto Event fornece uma maneira para um thread sinalizar que um evento ocorreu, e outros threads podem esperar por esse sinal.
Crie um projeto chamado event_object.py no WebIDE e insira o seguinte conteúdo.
import threading
## Create an event object
event = threading.Event()
## Define a function that waits for the event to be set
def my_function():
print("Waiting for event")
## Wait for the event to be set
event.wait()
print("Event received")
## Create a new thread with target function
thread = threading.Thread(target=my_function)
## Start the thread
thread.start()
## Signal the event after a few seconds
## The wait() call in the target function will now return and continue execution
event.set()
## Wait for the thread to finish executing
thread.join()
Neste exemplo, criamos um objeto Event usando a classe Event. Definimos uma função que espera que o evento seja sinalizado usando o método wait e, em seguida, imprime uma mensagem. Criamos um novo thread e o iniciamos. Após alguns segundos, sinalizamos o evento usando o método set. O thread recebe o evento e imprime uma mensagem. Finalmente, esperamos que o thread termine usando o método join.
Em seguida, use o seguinte comando para executar o script.
python event_object.py
Objeto Timer
Em Python, você pode usar o objeto threading.Timer para agendar uma função para ser executada após um tempo específico ter decorrido. O objeto Timer cria um novo thread que espera pelo intervalo de tempo especificado antes de executar a função.
Crie um projeto chamado timer_object.py no WebIDE e insira o seguinte conteúdo.
import threading
## Define a function to be executed by the Timer after 5 seconds
def my_function():
print("Hello from timer")
## Create a timer that runs the target function after 5 seconds
timer = threading.Timer(5, my_function)
## Start the timer
timer.start()
## Wait for the timer to finish
timer.join()
Neste exemplo, criamos um objeto Timer usando a classe Timer e passamos a ele um atraso de tempo em segundos e uma função para executar. Iniciamos o timer usando o método start e esperamos que ele termine usando o método join. Após 5 segundos, a função é executada e imprime uma mensagem.
Em seguida, use o seguinte comando para executar o script.
python timer_object.py
Dicas: O thread do timer é executado separadamente, portanto, pode não ser sincronizado com o thread principal. Se sua função depender de algum estado ou recursos compartilhados, você precisará sincronizar o acesso a eles de forma apropriada. Além disso, lembre-se de que o thread do timer não impedirá que o programa seja encerrado se ainda estiver em execução quando todos os outros threads não daemon forem concluídos.
Objeto Barreira
Em Python, você pode usar o objeto threading.Barrier para sincronizar múltiplos threads em pontos de sincronização predefinidos. O objeto Barrier fornece uma maneira para um conjunto de threads esperar uns pelos outros para atingir um determinado ponto em sua execução antes de continuar.
Crie um projeto chamado barrier_object.py no WebIDE e insira o seguinte conteúdo.
import threading
## Create a Barrier object for 3 threads
barrier = threading.Barrier(3)
## Define a function that waits at the barrier
def my_function():
print("Before barrier")
## Wait for all three threads to reach the barrier
barrier.wait()
print("After barrier")
## Create 3 threads using a loop and start them
threads = []
for i in range(3):
thread = threading.Thread(target=my_function)
threads.append(thread)
thread.start()
## Wait for all threads to finish executing
for thread in threads:
thread.join()
Neste exemplo, criamos um objeto Barrier usando a classe Barrier e passamos a ele o número de threads a serem esperados. Usando o método wait, definimos uma função que espera pela barreira e imprime uma mensagem. Criamos três threads e os iniciamos. Cada thread espera pela barreira, então todos os threads esperarão até que todos tenham atingido a barreira. Finalmente, esperamos que todos os threads terminem usando o método join.
Em seguida, use o seguinte comando para executar o script.
python barrier_object.py
Resumo
É isso! Agora você sabe como usar o módulo threading do Python em seu código. Ele pode nos ajudar a dominar os princípios e técnicas básicas da programação concorrente em profundidade, para que possamos desenvolver melhor aplicações concorrentes eficientes.



