2.1 Módulo threading
Multithreading em Python é uma maneira de executar várias threads (threads) ao mesmo tempo, o que permite usar os recursos do processador de forma mais eficiente, especialmente para operações de entrada e saída ou outras tarefas que podem ser executadas em paralelo.
Noções básicas de multithreading em Python:
Thread — é a menor unidade de execução que pode rodar em paralelo com outras threads em um processo. Todas as threads em um mesmo processo compartilham a memória, o que permite a troca de dados entre as threads.
Processo — é uma instância de um programa que é executada no sistema operacional, com seu próprio espaço de endereçamento e recursos. Ao contrário das threads, processos são isolados uns dos outros e trocam dados através de interação entre processos (IPC).
GIL — é um mecanismo no interpretador Python que impede a execução de várias threads Python simultaneamente. GIL garante a segurança da execução do código Python, mas limita o desempenho de programas multithread em processadores multinúcleo.
Importante! Lembre-se de que devido ao Global Interpreter Lock (GIL), o multithreading em Python pode não proporcionar um aumento significativo no desempenho para tarefas intensivas em computação, pois o GIL impede a execução simultânea de várias threads Python em processadores multinúcleo.
Módulo threading
Módulo threading em Python fornece uma interface de alto nível para trabalhar com threads. Ele permite criar e gerenciar threads, sincronizar e organizar a interação entre elas. Vamos dar uma olhada nos componentes chave e funções deste módulo em detalhes.
Componentes principais do módulo threading
Entidades para trabalhar com threads:
-
Thread— classe principal para criar e gerenciar threads. -
Timer— cronômetro para executar uma função após um tempo determinado. -
ThreadLocal— permite criar dados locais para a thread.
Mecanismo de sincronização de threads:
-
Lock— primitivo de sincronização para impedir acesso simultâneo a recursos compartilhados. -
Condition— variável condicional para sincronização mais complexa de threads. Event— primitivo para notificação entre threads.-
Semaphore— primitivo para limitar o número de threads que podem executar uma determinada seção simultaneamente. -
Barrier— sincroniza um número específico de threads, bloqueando-as até que todas alcancem a barreira.
A seguir, vou falar sobre 3 classes para trabalhar com threads, mas o mecanismo de sincronização de threads não vai ser necessário para você no futuro próximo.
2.2 Classe Thread
A classe Thread é a principal para criação e gestão das threads. Ela tem 4 métodos principais:
start(): Inicia a execução da thread.-
join(): A thread atual é suspensa e espera pela conclusão da thread iniciada. is_alive(): RetornaTrue, se a thread está em execução.-
run(): Método que contém o código que será executado na thread. Sobrescrito ao herdar da classeThread.
É bem mais simples do que parece — exemplo de uso da classe Thread.
Iniciando uma thread simples
import threading
def worker():
print("Worker thread is running")
# Criação de uma nova thread
t = threading.Thread(target=worker) #criamos um novo objeto thread
t.start() #Iniciamos a thread
t.join() # Aguardamos a conclusão da thread
print("Main thread is finished")
Após a chamada do método start, a função worker começará sua execução. Ou, mais precisamente, sua thread será adicionada à lista de threads ativas.
Uso de argumentos
import threading
def worker(number, text):
print(f"Worker {number}: {text}")
# Criação de nova thread com argumentos
t = threading.Thread(target=worker, args=(1, "Hello"))
t.start()
t.join()
Para passar parâmetros para uma nova thread, basta especificá-los como uma tupla e atribuir ao parâmetro args. Ao chamar a função indicada no target, os parâmetros serão passados automaticamente.
Sobrescrevendo o método run
import threading
class MyThread(threading.Thread):
def run(self):
print("Custom thread is running")
# Criação e execução da thread
t = MyThread()
t.start()
t.join()
Existem duas formas de indicar a função com a qual começar a execução da nova thread — você pode passá-la pelo parâmetro target ao criar o objeto Thread, ou herdar da classe Thread e sobrescrever a função run. Ambos métodos são considerados válidos e usados com frequência.
2.3 Classe Timer
A classe Timer no módulo threading é destinada a iniciar uma função após um determinado intervalo de tempo. Essa classe é útil para executar tarefas adiadas em um ambiente multithreading.
O cronômetro é criado e inicializado com a função que deve ser chamada e o tempo de atraso em segundos.
- Método
start()— inicia o cronômetro, que conta o intervalo de tempo definido e, em seguida, chama a função especificada. - Método
cancel()— permite interromper o cronômetro, se ele ainda não foi disparado. Isso é útil para evitar a execução da função, se o cronômetro já não for mais necessário.
Exemplos de uso:
Iniciando função com atraso
Neste exemplo, a função hello será chamada 5 segundos após o início do cronômetro.
import threading
def hello():
print("Hello, world!")
# Criação do cronômetro que chamará a função hello em 5 segundos
t = threading.Timer(5.0, hello)
t.start() # Início do cronômetro
Parando o cronômetro antes da execução
Aqui, o cronômetro será parado antes que a função hello possa ser executada, e, portanto, nada será impresso.
import threading
def hello():
print("Hello, world!")
# Criação do cronômetro
t = threading.Timer(5.0, hello)
t.start() # Início do cronômetro
# Parando o cronômetro antes da execução
t.cancel()
Cronômetro com argumentos
Neste exemplo, o cronômetro chamará a função greet em 3 segundos e passará o argumento "Alice".
import threading
def greet(name):
print(f"Hello, {name}!")
# Criação do cronômetro com argumentos
t = threading.Timer(3.0, greet, args=["Alice"])
t.start()
Classe Timer é útil para planejar a execução de tarefas após um tempo determinado. No entanto, os cronômetros não garantem a exatidão absoluta do tempo de execução, já que isso depende da carga do sistema e do agendador de threads.
2.4 Classe ThreadLocal
Classe ThreadLocal se destina a criar threads que têm seus próprios dados locais. Isso é útil em aplicativos multithreading, quando cada thread deve ter sua própria versão dos dados para evitar conflitos e problemas de sincronização.
Cada thread que usa ThreadLocal terá suas próprias cópias independentes dos dados. Os dados salvos no objeto ThreadLocal são exclusivos de cada thread e não são compartilhados com outras threads. Isso é útil para armazenar dados que são utilizados apenas no contexto de uma thread, como o usuário atual em um aplicativo web ou a conexão atual com o banco de dados.
Exemplos de uso:
Uso principal
Neste exemplo, cada thread atribui seu nome à variável local value e a imprime. O valor de value é exclusivo para cada thread.
import threading
# Criação de um objeto ThreadLocal
local_data = threading.local()
def process_data():
# Atribuição de valor à variável local da thread
local_data.value = threading.current_thread().name
# Acesso à variável local da thread
print(f'Value in {threading.current_thread().name}: {local_data.value}')
threads = []
for i in range(5):
t = threading.Thread(target=process_data)
threads.append(t)
t.start()
for t in threads:
t.join()
Armazenamento de dados do usuário em aplicativo web
Neste exemplo, cada thread processa uma solicitação para seu usuário. O valor de user_data.user é exclusivo para cada thread.
import threading
# Criação de um objeto ThreadLocal
user_data = threading.local()
def process_request(user):
# Atribuição de valor à variável local da thread
user_data.user = user
handle_request()
def handle_request():
# Acesso à variável local da thread
print(f'Handling request for user: {user_data.user}')
threads = []
users = ['Alice', 'Bob', 'Charlie']
for user in users:
t = threading.Thread(target=process_request, args=(user,))
threads.append(t)
t.start()
for t in threads:
t.join()
Estes foram os 3 classes mais úteis da biblioteca threading. É provável que você os use no seu trabalho, mas os outros dificilmente. Agora, todos estão migrando para as funções assíncronas e a biblioteca asyncio. É sobre ela que vamos falar nos próximos tempos.
GO TO FULL VERSION