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

Может ли многопоточность быть запущена на разных ядрах процесса?

2.0 Middle🔥 161 комментариев
#Python Core#Асинхронность и многопоточность

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

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

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

Многопоточность и GIL в Python

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

Что такое GIL?

Global Interpreter Lock — это мьютекс в CPython, который защищает доступ к объектам Python. Только один поток может выполнять Python код одновременно, даже если у тебя 16-ядерный процессор.

import threading
import time

def cpu_bound_task():
    """Задача, зависящая от CPU (вычисления)"""
    total = 0
    for i in range(100_000_000):
        total += i
    return total

def single_thread():
    """Однопоточное выполнение"""
    start = time.time()
    cpu_bound_task()
    cpu_bound_task()
    return time.time() - start

def multi_thread():
    """Многопоточное выполнение — не будет быстрее!"""
    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()
    return time.time() - start

print(f"Однопоток: {single_thread():.2f}s")  # ~5 сек
print(f"Многопоток: {multi_thread():.2f}s")  # ~5 сек (не быстрее!)

Видишь? Многопоточность здесь не помогает вообще. Оба занимают одно и то же время.

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

GIL был создан для упрощения внутреннего управления памятью в CPython. Без него нужна была бы сложная система лок-фри структур данных или fine-grained locking, что замедлило бы весь интерпретатор. Это исторический выбор дизайна.

Когда многопоточность всё же помогает?

Многопоточность полезна для I/O-bound задач, потому что при ожидании I/O (сетевой запрос, чтение файла), Python отпускает GIL.

import threading
import time
import requests

def fetch_url(url):
    """Задача, зависящая от I/O"""
    response = requests.get(url)
    return response.status_code

def single_thread_io():
    """Однопоточно"""
    start = time.time()
    for url in [http://example.com] * 10:
        fetch_url(url)
    return time.time() - start

def multi_thread_io():
    """Многопоточно — будет НАМНОГО быстрее!"""
    start = time.time()
    threads = []
    for url in [http://example.com] * 10:
        t = threading.Thread(target=fetch_url, args=(url,))
        t.start()
        threads.append(t)
    for t in threads:
        t.join()
    return time.time() - start

print(f"Однопоток I/O: {single_thread_io():.2f}s")  # ~10 сек
print(f"Многопоток I/O: {multi_thread_io():.2f}s")  # ~1 сек (в 10 раз быстрее!)

Здесь многопоточность экономит время, потому что пока один поток ждёт ответа от сервера, другие потоки могут выполняться.

Альтернативы GIL

1. Multiprocessing — разные процессы, разные GIL

from multiprocessing import Pool

def cpu_task(n):
    return sum(range(n * 1_000_000, (n+1) * 1_000_000))

if __name__ == __main__:
    with Pool(processes=4) as pool:
        results = pool.map(cpu_task, range(4))
    # Теперь работает параллельно на разных ядрах!

Multiprocessing создаёт отдельный процесс для каждого задания. Каждый процесс имеет собственный GIL, поэтому работает по-настоящему параллельно. Минус: большие overhead при создании процесса и сложность синхронизации.

2. Asyncio — асинхронное программирование

import asyncio
import aiohttp

async def fetch_url(session, url):
    async with session.get(url) as response:
        return response.status

async def fetch_many():
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, http://example.com) for _ in range(10)]
        return await asyncio.gather(*tasks)

# Работает на одном потоке, но с меньшим overhead, чем threading
asyncio.run(fetch_many())

Асинхронность более эффективна для I/O, чем многопоточность. Здесь нет context switching между потоками, только явная передача управления при await.

3. Python 3.13+ — без GIL

Na Python 3.13 добавлена экспериментальная поддержка выключения GIL. Это большой шаг, и скоро многопоточность станет действительно параллельной.

Практические рекомендации

  • CPU-bound: используй multiprocessing или вообще C-расширения (Cython, NumPy)
  • I/O-bound: threading или asyncio (asyncio быстрее)
  • Реал-тайм обработка: распредели на разные процессы или используй другой язык (Go, Rust)

Вывод

Многопоточность в Python не может работать на разных ядрах для CPU-bound задач из-за GIL. Для I/O-bound задач потоки работают хорошо, потому что GIL отпускается при ожидании. Для истинного параллелизма нужно использовать multiprocessing.

Может ли многопоточность быть запущена на разных ядрах процесса? | PrepBro