Threading Fácil de Usar

PythonBeginner
Pratique Agora

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:

  1. Classe Thread: Esta classe representa um único thread de execução.
  2. 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:

  1. Criamos uma subclasse da classe Thread e sobrescrevemos o método run para definir o comportamento do thread. Crie um projeto chamado thread_subclass.py no 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
  1. Criamos um thread e passamos argumentos para a função alvo usando o parâmetro args. Crie um projeto chamado thread_with_args.py no 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 bloco with for 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.