Sobes.tech
Middle

O que é GIL (Global Interpreter Lock) em Python?

sobes.tech IA

Resposta da IA

GIL (Global Interpreter Lock) é um mecanismo no interpretador CPython que permite a execução de apenas um thread (fio) de cada vez. Mesmo em processadores multinúcleo, o GIL não permite que os threads paralelizem realmente a execução do código Python.

Razões para a existência do GIL:

  • Simplificação da gestão de memória: O GIL simplifica a implementação de uma gestão de memória segura para threads e a recolha de lixo (contagem de referências). Sem o GIL, seriam necessários mecanismos de bloqueio mais complexos e caros em recursos para proteger estruturas de dados partilhadas.
  • Compatibilidade com bibliotecas em C: O GIL facilita a integração com bibliotecas existentes em C, que podem não ser seguras para threads.

Impacto do GIL:

  • Limitação do paralelismo em tarefas CPU-bound: Para tarefas que utilizam intensamente o processador, o GIL torna-se num gargalo, pois mesmo em sistemas multinúcleo, a execução não é paralelizada entre núcleos. Os threads têm de esperar que o GIL seja libertado.
  • Menor impacto em tarefas I/O-bound: Para tarefas que utilizam intensamente entrada/saída (requisições de rede, trabalho com ficheiros), o GIL não é tão crítico. Durante a espera por operações I/O, o thread liberta o GIL, permitindo que outros threads trabalhem.

Formas de contornar o GIL:

  • Multiprocessamento: Em vez de threads, são utilizados processos independentes. Cada processo tem a sua própria instância do interpretador Python e, consequentemente, o seu próprio GIL. Os processos podem ser executados em paralelo em diferentes núcleos.
  • Programação assíncrona (asyncio): Utiliza um modelo de entrada/saída assíncrona num único thread. Em vez de criar múltiplos threads, o código assíncrono alterna entre tarefas durante a espera de operações I/O. O GIL não afeta tanto, pois não há execução paralela de tarefas relacionadas com a CPU.
  • Bibliotecas em C/C++: Partes do código escritas em C ou C++ podem realizar operações sem manter o GIL, libertando-o durante a execução dessas operações. Bibliotecas populares como NumPy e SciPy usam isso para cálculos eficientes.
# Exemplo do impacto do GIL numa tarefa CPU-bound
import threading
import time

def cpu_bound_task():
    count = 0
    for i in range(10**7):
        count += 1

# Criação de dois threads para executar uma tarefa CPU-bound
t1 = threading.Thread(target=cpu_bound_task)
t2 = threading.Thread(target=cpu_bound_task)

start_time = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end_time = time.time()

print(f"Tempo de execução com threads (CPU-bound): {end_time - start_time:.2f} segundos") # O tempo será aproximadamente o dobro do que a execução de uma única tarefa, devido ao GIL

# Exemplo de tarefa I/O-bound, onde o GIL é menos visível
import threading
import time

def io_bound_task():
    time.sleep(1) # Simulação de espera I/O

# Criação de dois threads para executar uma tarefa I/O-bound
t1 = threading.Thread(target=io_bound_task)
t2 = threading.Thread(target=io_bound_task)

start_time = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end_time = time.time()

print(f"Tempo de execução com threads (I/O-bound): {end_time - start_time:.2f} segundos") # O tempo será cerca de 1 segundo, pois os threads liberam o GIL durante sleep