Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Industrial programming >> python

Dominando Multithreading em Python: GIL Explicado com Exemplos Práticos

Python oferece tanto multiprocessing quanto multithreading para acelerar cargas de trabalho. Este guia percorre a criação de aplicações robustas baseadas em threads, as nuances do Global Interpreter Lock (GIL) e como evitar armadilhas comuns como deadlocks e race conditions.

O que é uma Thread?

Uma thread é uma unidade leve de execução dentro de um único processo. Threads compartilham o espaço de memória do processo, permitindo troca eficiente de dados, enquanto o sistema operacional as agenda para rodar concorrentemente em um ou mais núcleos de CPU.

O que é um Processo?

Um processo é uma instância de programa independente. Quando você lança uma aplicação — digamos um navegador ou editor de texto — o OS cria um processo separado com seu próprio código, dados e mapa de memória.

Fundamentos de Multithreading em Python

O módulo threading do Python fornece uma API de alto nível para gerenciar threads, enquanto o módulo legado _thread é depreciado e retido apenas para compatibilidade retroativa. Threads são ideais para tarefas I/O-bound, enquanto trabalho CPU-bound deve usar o módulo multiprocessing para contornar o GIL.

Quando Usar Multithreading

Multithreading brilha quando você precisa realizar várias operações I/O-pesadas — requisições de rede, I/O de arquivo ou chamadas de banco de dados — simultaneamente. Threads bem projetadas podem melhorar responsividade e throughput sem o overhead de spawnar novos processos.

Principais Módulos Python para Threading

  1. _thread — Low-level, depreciado.
  2. threading — Moderno, rico em recursos e a escolha recomendada.

Usando o Módulo threading

O módulo threading oferece uma classe Thread, primitivas de sincronização (Lock, RLock, Semaphore, Condition, Event, Barrier) e funções utilitárias como activeCount() e enumerate(). Abaixo está um exemplo mínimo demonstrando criação de thread e sincronização.

import time
import _thread
def thread_test(name, wait):
    for i in range(4):
        time.sleep(wait)
        print(f"Running {name}")
    print(f"{name} has finished execution")
if __name__ == "__main__":
    _thread.start_new_thread(thread_test, ("First Thread", 1))
    _thread.start_new_thread(thread_test, ("Second Thread", 2))
    _thread.start_new_thread(thread_test, ("Third Thread", 3))

Executar este código produz saída intercalada demonstrando execução concorrente.

Dominando Multithreading em Python: GIL Explicado com Exemplos Práticos

Explicação

  1. Importe time e o legado _thread.
  2. Defina thread_test para realizar trabalho e logar progresso.
  3. Inicie três threads independentes via start_new_thread.

Threading Moderno com threading.Thread

Na prática, você fará subclass de threading.Thread e sobrescreverá seu método run(). O exemplo a seguir espelha o comportamento anterior mas usa a API de alto nível.

import time
import threading
class ThreadTester(threading.Thread):
    def __init__(self, name, delay):
        super().__init__()
        self.name = name
        self.delay = delay
    def run(self):
        for _ in range(4):
            time.sleep(self.delay)
            print(f"Running {self.name}")
        print(f"{self.name} has finished execution")
if __name__ == "__main__":
    threads = [
        ThreadTester("First Thread", 1),
        ThreadTester("Second Thread", 2),
        ThreadTester("Third Thread", 3),
    ]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

Saída:

Dominando Multithreading em Python: GIL Explicado com Exemplos Práticos

Explicação

  1. Subclasse Thread para encapsular comportamento de thread.
  2. Sobrescreva __init__ para armazenar dados específicos da thread.
  3. Sobrescreva run para definir a tarefa.
  4. Instancie, inicie e dê join em cada thread.

Armadilhas Comuns de Threading

Deadlocks

Um deadlock ocorre quando duas ou mais threads esperam indefinidamente por recursos mantidos uma pela outra. Ilustração clássica: o problema dos Filósofos Jantando — cinco filósofos competem por cinco garfos, cada um precisando de dois garfos para comer. Se todo filósofo pegar o garfo da direita primeiro, uma espera circular surge, paralisando todo progresso.

Race Conditions

Quando múltiplas threads modificam dados compartilhados sem coordenação adequada, o estado final pode ser imprevisível. Por exemplo:

i = 0
for _ in range(100):
    print(i)
    i += 1

Executando este loop em threads paralelas pode produzir sequência embaralhada porque incrementos se sobrepõem.

Sincronizando Threads

O módulo threading fornece várias primitivas de sincronização. A mais fundamental é Lock, que garante acesso exclusivo a um recurso compartilhado.

import threading
lock = threading.Lock()
def worker(name):
    for _ in range(5):
        lock.acquire()
        print(f"{name} acquired lock")
        # critical section
        lock.release()
if __name__ == "__main__":
    t1 = threading.Thread(target=worker, args=("Thread A",))
    t2 = threading.Thread(target=worker, args=("Thread B",))
    t1.start(); t2.start()
    t1.join(); t2.join()

Saída demonstra acesso serializado à seção crítica.

Dominando Multithreading em Python: GIL Explicado com Exemplos Práticos

Outras Ferramentas de Sincronização

Entendendo o Global Interpreter Lock (GIL)

CPython usa o GIL para serializar execução de bytecode entre threads. Embora isso proteja estruturas internas do interpretador (ex: reference counting) de race conditions, também limita paralelismo para tarefas CPU-bound em sistemas multi-core.

  1. Quando uma thread inicia, ela adquire o GIL.
  2. Se outra thread precisa do GIL, ela bloqueia até a primeira liberá-lo.
  3. Threads I/O-bound liberam o GIL durante chamadas bloqueantes, permitindo outras rodarem.
  4. Threads CPU-bound seguram o GIL, impedindo outras threads de executarem código Python.

Consequentemente, multithreading em Python rende pouco benefício para cargas de trabalho computacionalmente pesadas; multiprocessing é a abordagem preferida nesses casos.

Por que o GIL Existe

O garbage collector do CPython depende de reference counting. Sem um lock global, atualizações concorrentes de contadores de referência levariam a race conditions e corrupção de memória. Um lock por objeto introduziria overhead significativo, então o GIL foi adotado como solução simples e efetiva.

Conclusões Práticas

python

  1. Dominando a Função free() em C: Guia Prático e Exemplo
  2. Dominando o Método strip() do Python: Guia Completo e Exemplos Práticos
  3. Domine o str.count() do Python: Como Contar Caracteres e Substrings com Exemplos
  4. Função round() em Python Explicada com Exemplos Práticos
  5. Dominando a Função map() do Python: Sintaxe, Exemplos e Boas Práticas
  6. Python timeit(): Medindo Tempo de Execução com Exemplos Práticos
  7. Counter do Python em collections: Contagem Eficiente, Atualização e Operações Aritméticas
  8. Python list.count(): Guia Especializado com Exemplos Práticos
  9. Python List index(): Como Encontrar Posições de Elementos com Exemplos Práticos
  10. Dominando Multithreading em C#: Aumente Performance e Eficiência