CodeGym /Cursos /Python SELF PT /Módulo threading

Módulo threading

Python SELF PT
Nível 25 , Lição 1
Disponível

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(): Retorna True, 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 classe Thread.

É 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.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION