Free-threaded Python убрал GIL, но NumPy всё равно упирался во… — 📚Python Books — TG.ME

🐍 Free-threaded Python убрал GIL, но NumPy всё равно упирался во внутренние блокировки

Казалось бы, Python 3.13+ без GIL должен позволить нескольким потокам одновременно выполнять численные операции.

Но на практике параллельные ufunc начинали конкурировать за внутренние структуры NumPy, счётчики ссылок и выделение памяти.

В NumPy 2.5 разработчики устранили несколько ключевых узких мест:

- таблицу диспетчеризации ufunc заменили на lock-free concurrent hash map;
- часть общих служебных объектов сделали «бессмертными», снизив конкуренцию при подсчёте ссылок;
- выделение памяти перевели на PyMem_RawMalloc и PyMem_RawFree.

Теперь независимые операции можно масштабировать обычными потоками:


from concurrent.futures import ThreadPoolExecutor

import numpy as np

arrays = [
np.random.rand(5_000_000)
for _ in range(8)
]

with ThreadPoolExecutor(max_workers=8) as pool:
results = list(pool.map(np.sin, arrays))


Главное преимущество перед multiprocessing - массивы не нужно сериализовать и копировать между процессами.

Потоки работают в одном адресном пространстве, поэтому могут эффективнее использовать многоядерный CPU и общую память.

Но есть важная оговорка:

free-threaded не означает автоматически thread-safe.

Одновременное изменение одного ndarray из нескольких потоков всё ещё может привести к гонкам, повреждённым данным и падению интерпретатора.

Безопаснее использовать:

- отдельный массив для каждого worker;
- общий массив только для чтения;
- явную синхронизацию при записи.

Удалить GIL оказалось недостаточно. Чтобы Python действительно масштабировался по ядрам, пришлось перепроектировать и внутренности NumPy.

Подробнее:
https://labs.quansight.org/blog/scaling-numpy-on-free-threaded-python
August 6, 2026 4.9K 11