← Назад к вопросам

Кто следит за тем, чтобы потоки работали в Python?

1.0 Junior🔥 141 комментариев
#Python Core#Soft Skills#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Кто следит за тем, чтобы потоки работали в Python?

Главный ответ: GIL (Global Interpreter Lock)

В CPython за управление потоками отвечает GIL (Global Interpreter Lock) - глобальная блокировка интерпретатора. Это один из ключевых механизмов, который определяет поведение многопоточности в Python.

Что такое GIL?

GIL - это мьютекс (взаимное исключение), который защищает доступ к объектам в CPython. Только один поток может выполнять Python bytecode одновременно, даже если у вас многоядерный процессор.

import threading
import time

def worker(name):
    for i in range(5):
        print(f"Поток {name}: итерация {i}")
        time.sleep(0.1)

# Создаешь два потока
t1 = threading.Thread(target=worker, args=("A",))
t2 = threading.Thread(target=worker, args=("B",))

t1.start()
t2.start()

# GIL следит за тем, что оба потока не выполняют
# Python bytecode одновременно

Почему существует GIL?

  1. Простота управления памятью - CPython использует reference counting для управления памятью. GIL предотвращает необходимость в дорогостоящих блокировках на каждый объект.

  2. Упрощение расширений C - многие расширения C для Python не потокобезопасны. GIL позволяет им работать без изменений.

  3. Быстродействие - однопоточные программы выполняются быстрее благодаря отсутствию затрат на синхронизацию.

Как работает GIL?

# Визуально так работает GIL:
#
# Время →
# Поток A: [работает] [ждет GIL] [работает] [ждет GIL]
# Поток B: [ждет GIL] [работает] [ждет GIL] [работает]
#          ^        ^
#        GIL        GIL отпущена
#        получена
#
# GIL переключается между потоками примерно каждые
# 5ms (можно настроить через sys.setswitchinterval())

Проблема GIL для многопоточности

import threading
import time

def cpu_bound_task():
    """Задача, требующая процессорных ресурсов"""
    total = 0
    for i in range(100_000_000):
        total += i
    return total

# Однопоточный вариант
start = time.time()
cpu_bound_task()
print(f"Один поток: {time.time() - start:.2f}s")

# Двухпоточный вариант
start = time.time()
t1 = threading.Thread(target=cpu_bound_task)
t2 = threading.Thread(target=cpu_bound_task)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"Два потока: {time.time() - start:.2f}s")

# РЕЗУЛЬТАТ: Два потока будут медленнее!
# Потому что GIL не позволяет им работать параллельно

Когда GIL не блокирует?

GIL отпускается в нескольких случаях:

import threading
import time

# 1. Во время I/O операций (самое важное!)
def io_task():
    time.sleep(1)  # GIL ОТПУЩЕНА во время sleep!
    return "done"

# 2. В некоторых операциях с NumPy
import numpy as np
arr = np.arange(1000000)
arr.sum()  # GIL может быть отпущена

# 3. При работе с С-расширениями
import zlib
compressed = zlib.compress(b"data" * 10000)  # GIL отпущена

Пример: I/O-bound vs CPU-bound

import threading
import time

# I/O-BOUND задача (многопоточность помогает)
def download_file(url, delay):
    time.sleep(delay)  # Имитация загрузки
    return f"Downloaded {url}"

start = time.time()
threads = [
    threading.Thread(target=download_file, args=(f"url{i}", 0.5))
    for i in range(4)
]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(f"I/O-bound: {time.time() - start:.2f}s")  # ~0.5s (параллель работает!)

# CPU-BOUND задача (многопоточность НЕ помогает)
def calculate():
    return sum(range(100_000_000))

start = time.time()
threads = [
    threading.Thread(target=calculate)
    for _ in range(4)
]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(f"CPU-bound: {time.time() - start:.2f}s")  # Медленнее одного потока!

Решения для обхода GIL

1. Используй multiprocessing для CPU-bound задач:

from multiprocessing import Process
import time

def cpu_task():
    return sum(range(100_000_000))

start = time.time()
processes = [Process(target=cpu_task) for _ in range(4)]
for p in processes:
    p.start()
for p in processes:
    p.join()
print(f"Multiprocessing: {time.time() - start:.2f}s")  # Работает параллельно!

2. Используй asyncio для I/O задач:

import asyncio

async def async_task(name):
    print(f"Начало {name}")
    await asyncio.sleep(1)
    print(f"Конец {name}")

async def main():
    await asyncio.gather(
        async_task("A"),
        async_task("B"),
        async_task("C")
    )

start = time.time()
asyncio.run(main())
print(f"Asyncio: {time.time() - start:.2f}s")  # ~1s (параллель!)

3. Используй альтернативные интерпретаторы:

  • Jython - GIL нет
  • IronPython - GIL нет
  • PyPy - есть GIL, но легче управляется
  • Python 3.13+ - экспериментальная поддержка без GIL

Вывод

В Python за управление потоками отвечает GIL (Global Interpreter Lock). Он позволяет только одному потоку выполнять Python bytecode одновременно. Для I/O-bound задач многопоточность все равно полезна, потому что GIL отпускается во время ожидания. Для CPU-bound задач нужно использовать multiprocessing или asyncio.

Кто следит за тем, чтобы потоки работали в Python? | PrepBro