Sobes.tech
Назад к вопросам
Junior — Middle
52

Почему использование многопоточности в Python не приводит к заметному ускорению при выполнении тяжелых CPU-зависимых вычислений?

Ответ от нейросети

sobes.tech AI

Что хотят услышать интервьюеры:

В CPython многопоточность почти не ускоряет CPU-bound задачи из-за GIL — глобальной блокировки интерпретатора. В один момент байткод Python выполняет только один поток, даже если ядер несколько. Поэтому потоки полезнее для I/O, чем для тяжелых вычислений.

Определение:

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

Пример использования:

Если нужно посчитать, например, большой объем хэшей, перебрать миллионы элементов или выполнить сложные численные расчеты на Python, запуск нескольких потоков обычно не даст линейного ускорения. Для такого случая чаще используют multiprocessing или выносят вычисления в C/C++/NumPy, где тяжелая работа выполняется вне GIL.

from concurrent.futures import ThreadPoolExecutor
from multiprocessing import Pool
import hashlib

data = [b"sample"] * 1_000_000

def work(x):
    return hashlib.sha256(x).hexdigest()

# Потоки: для CPU-bound обычно не дают заметного ускорения
with ThreadPoolExecutor(max_workers=4) as executor:
    results_threads = list(executor.map(work, data))

# Процессы: могут использовать несколько ядер
with Pool(processes=4) as pool:
    results_processes = pool.map(work, data)

Пояснение кода:

В примере одна и та же CPU-зависимая функция work вызывается на большом массиве данных. В варианте с ThreadPoolExecutor несколько потоков существуют, но выполнение Python-байткода все равно сериализуется GIL, поэтому ускорение обычно минимальное. В варианте с Pool создаются отдельные процессы, у каждого свой интерпретатор и свой GIL, поэтому вычисления действительно могут идти параллельно на разных ядрах.

Ключевые моменты:

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