> **Источник:** https://python-all.ru/3.16/howto/free-threading-extensions.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# Поддержка расширений C API для свободной многопоточности

Начиная с версии 3.13, CPython поддерживает работу с отключённой [глобальной блокировкой интерпретатора](https://python-all.ru/3.16/glossary.html#term-global-interpreter-lock) (GIL) в конфигурации, называемой [свободная многопоточность](https://python-all.ru/3.16/glossary.html#term-free-threading). В этом документе описывается, как адаптировать расширения C API для поддержки свободной многопоточности.

## Определение сборки со свободной многопоточностью в C

C API CPython предоставляет макрос `Py_GIL_DISABLED`: в сборке со свободной многопоточностью он определён как `1`, а в обычной сборке не определён. Его можно использовать для включения кода, который выполняется только в сборке со свободной многопоточностью:

```c
#ifdef Py_GIL_DISABLED
/* код, который выполняется только в сборке со свободной многопоточностью */
#endif
```

> **Примечание**
>
> В Windows этот макрос не определяется автоматически, его нужно указать компилятору при сборке. Функция [`sysconfig.get_config_var()`](https://python-all.ru/3.16/library/sysconfig.html#sysconfig.get_config_var) может использоваться для проверки, был ли макрос определён в текущем запущенном интерпретаторе.

## Инициализация модуля

Модули расширения должны явно указать, что они поддерживают работу с отключённым GIL; иначе при импорте расширения будет выдано предупреждение и GIL будет включён во время выполнения.

Есть два способа указать, что модуль расширения поддерживает работу с отключённым GIL, в зависимости от того, использует ли расширение многофазную или однофазную инициализацию.

### Многофазная инициализация

Расширения, использующие [многофазную инициализацию](https://python-all.ru/3.16/c-api/extension-modules.html#multi-phase-initialization) (функции типа [`PyModuleDef_Init()`](https://python-all.ru/3.16/c-api/extension-modules.html#c.PyModuleDef_Init), [`PyModExport_*`](https://python-all.ru/3.16/c-api/extension-modules.html#c.PyModExport_modulename) экспортный хук, [`PyModule_FromSlotsAndSpec()`](https://python-all.ru/3.16/c-api/module.html#c.PyModule_FromSlotsAndSpec)), должны добавить слот [`Py_mod_gil`](https://python-all.ru/3.16/c-api/module.html#c.Py_mod_gil) в определение модуля. Если ваше расширение поддерживает старые версии CPython, следует защитить этот слот проверкой [`PY_VERSION_HEX`](https://python-all.ru/3.16/c-api/apiabiversion.html#c.PY_VERSION_HEX).

```c
static struct PyModuleDef_Slot module_slots[] = {
    ...
#if PY_VERSION_HEX >= 0x030D0000
    {Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
    {0, NULL}
};
```

### Однофазная инициализация

Расширения, использующие устаревшую [однофазную инициализацию](https://python-all.ru/3.16/c-api/extension-modules.html#single-phase-initialization) (то есть [`PyModule_Create()`](https://python-all.ru/3.16/c-api/module.html#c.PyModule_Create)), должны вызывать [`PyUnstable_Module_SetGIL()`](https://python-all.ru/3.16/c-api/module.html#c.PyUnstable_Module_SetGIL), чтобы указать, что они поддерживают работу с отключённым GIL. Эта функция определена только в сборке со свободными потоками, поэтому следует защитить вызов с помощью `#ifdef Py_GIL_DISABLED`, чтобы избежать ошибок компиляции в обычной сборке.

```c
static struct PyModuleDef moduledef = {
    PyModuleDef_HEAD_INIT,
    ...
};

PyMODINIT_FUNC
PyInit_mymodule(void)
{
    PyObject *m = PyModule_Create(&moduledef);
    if (m == NULL) {
        return NULL;
    }
#ifdef Py_GIL_DISABLED
    PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
    return m;
}
```

## Общие рекомендации по API

Большая часть C API потокобезопасна, но есть некоторые исключения.

- **Поля структур**: прямой доступ к полям объектов C API Python или структур не является потокобезопасным, если поле может быть одновременно изменено.
- **Макросы**: макросы-аксессоры, такие как [`PyList_GET_ITEM`](https://python-all.ru/3.16/c-api/list.html#c.PyList_GET_ITEM), [`PyList_SET_ITEM`](https://python-all.ru/3.16/c-api/list.html#c.PyList_SET_ITEM), и макросы вроде [`PySequence_Fast_GET_SIZE`](https://python-all.ru/3.16/c-api/sequence.html#c.PySequence_Fast_GET_SIZE), которые используют объект, возвращаемый [`PySequence_Fast()`](https://python-all.ru/3.16/c-api/sequence.html#c.PySequence_Fast), не выполняют ни проверки ошибок, ни блокировки. Эти макросы не являются потокобезопасными, если объект-контейнер может изменяться одновременно.
- **Заимствованные ссылки**: функции C API, возвращающие [заимствованные ссылки](https://python-all.ru/3.16/glossary.html#term-borrowed-reference), могут не быть потокобезопасными, если содержащий объект изменяется одновременно. См. раздел [заимствованные ссылки](https://python-all.ru/3.16/howto/free-threading-extensions.html#id2) для получения дополнительной информации.

### Потокобезопасность контейнеров

Контейнеры, такие как [`PyListObject`](https://python-all.ru/3.16/c-api/list.html#c.PyListObject), [`PyDictObject`](https://python-all.ru/3.16/c-api/dict.html#c.PyDictObject) и [`PySetObject`](https://python-all.ru/3.16/c-api/set.html#c.PySetObject), выполняют внутреннюю блокировку в сборке со свободными потоками. Например, [`PyList_Append()`](https://python-all.ru/3.16/c-api/list.html#c.PyList_Append) будет блокировать список перед добавлением элемента.

#### `PyDict_Next`

Заметным исключением является [`PyDict_Next()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_Next), который не блокирует словарь. Следует использовать [`Py_BEGIN_CRITICAL_SECTION`](https://python-all.ru/3.16/c-api/synchronization.html#c.Py_BEGIN_CRITICAL_SECTION) для защиты словаря при итерации по нему, если словарь может быть одновременно изменён:

```c
Py_BEGIN_CRITICAL_SECTION(dict);
PyObject *key, *value;
Py_ssize_t pos = 0;
while (PyDict_Next(dict, &pos, &key, &value)) {
    ...
}
Py_END_CRITICAL_SECTION();
```

## Заимствованные ссылки

Некоторые функции C API возвращают [заимствованные ссылки](https://python-all.ru/3.16/glossary.html#term-borrowed-reference). Эти API не являются потокобезопасными, если содержащий объект изменяется одновременно. Например, небезопасно использовать [`PyList_GetItem()`](https://python-all.ru/3.16/c-api/list.html#c.PyList_GetItem), если список может быть одновременно изменён.

В следующей таблице перечислены некоторые API заимствованных ссылок и их замены, которые возвращают [сильные ссылки](https://python-all.ru/3.16/glossary.html#term-strong-reference).

| API заимствованных ссылок | API сильных ссылок |
| --- | --- |
| [`PyList_GetItem()`](https://python-all.ru/3.16/c-api/list.html#c.PyList_GetItem) | [`PyList_GetItemRef()`](https://python-all.ru/3.16/c-api/list.html#c.PyList_GetItemRef) |
| [`PyList_GET_ITEM()`](https://python-all.ru/3.16/c-api/list.html#c.PyList_GET_ITEM) | [`PyList_GetItemRef()`](https://python-all.ru/3.16/c-api/list.html#c.PyList_GetItemRef) |
| [`PyDict_GetItem()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_GetItem) | [`PyDict_GetItemRef()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_GetItemRef) |
| [`PyDict_GetItemWithError()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_GetItemWithError) | [`PyDict_GetItemRef()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_GetItemRef) |
| [`PyDict_GetItemString()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_GetItemString) | [`PyDict_GetItemStringRef()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_GetItemStringRef) |
| [`PyDict_SetDefault()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_SetDefault) | [`PyDict_SetDefaultRef()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_SetDefaultRef) |
| [`PyDict_Next()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_Next) | нет (см. [PyDict\_Next](https://python-all.ru/3.16/howto/free-threading-extensions.html#pydict-next)) |
| `PyWeakref_GetObject()` | [`PyWeakref_GetRef()`](https://python-all.ru/3.16/c-api/weakref.html#c.PyWeakref_GetRef) |
| `PyWeakref_GET_OBJECT()` | [`PyWeakref_GetRef()`](https://python-all.ru/3.16/c-api/weakref.html#c.PyWeakref_GetRef) |
| [`PyImport_AddModule()`](https://python-all.ru/3.16/c-api/import.html#c.PyImport_AddModule) | [`PyImport_AddModuleRef()`](https://python-all.ru/3.16/c-api/import.html#c.PyImport_AddModuleRef) |
| [`PyCell_GET()`](https://python-all.ru/3.16/c-api/cell.html#c.PyCell_GET) | [`PyCell_Get()`](https://python-all.ru/3.16/c-api/cell.html#c.PyCell_Get) |

Не все API, возвращающие заимствованные ссылки, проблематичны. Например, [`PyTuple_GetItem()`](https://python-all.ru/3.16/c-api/tuple.html#c.PyTuple_GetItem) безопасен, так как кортежи неизменяемы. Аналогично, не все варианты использования вышеперечисленных API проблематичны. Например, [`PyDict_GetItem()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_GetItem) часто используется для разбора словарей именованных аргументов в вызовах функций; эти словари именованных аргументов фактически являются приватными (недоступны другим потокам), поэтому использование заимствованных ссылок в этом контексте безопасно.

Некоторые из этих функций были добавлены в Python 3.13. Можно использовать пакет [pythoncapi-compat](https://python-all.ru/3.16/howto/free-threading-extensions.html), чтобы предоставить реализации этих функций для старых версий Python.

## API выделения памяти

C API управления памятью Python предоставляет функции в трёх разных [доменах выделения](https://python-all.ru/3.16/c-api/memory.html#id1): "raw", "mem" и "object". Для обеспечения потокобезопасности в сборке со свободными потоками требуется, чтобы только объекты Python выделялись с использованием домена object, и все объекты Python выделялись с использованием этого домена. Это отличается от предыдущих версий Python, где это было лишь рекомендацией, а не строгим требованием.

> **Примечание**
>
> Найдите использования [`PyObject_Malloc()`](https://python-all.ru/3.16/c-api/memory.html#c.PyObject_Malloc) в своём расширении и проверьте, что выделенная память используется для объектов Python. Используйте [`PyMem_Malloc()`](https://python-all.ru/3.16/c-api/memory.html#c.PyMem_Malloc) для выделения буферов вместо `PyObject_Malloc()`.

## API состояния потока и GIL

Python предоставляет набор функций и макросов для управления состоянием потока и GIL, например:

- [`PyThreadState_Ensure()`](https://python-all.ru/3.16/c-api/threads.html#c.PyThreadState_Ensure), [`PyThreadState_EnsureFromView()`](https://python-all.ru/3.16/c-api/threads.html#c.PyThreadState_EnsureFromView) и [`PyThreadState_Release()`](https://python-all.ru/3.16/c-api/threads.html#c.PyThreadState_Release)
- [`PyGILState_Ensure()`](https://python-all.ru/3.16/c-api/threads.html#c.PyGILState_Ensure) и [`PyGILState_Release()`](https://python-all.ru/3.16/c-api/threads.html#c.PyGILState_Release)
- [`PyEval_SaveThread()`](https://python-all.ru/3.16/c-api/threads.html#c.PyEval_SaveThread) и [`PyEval_RestoreThread()`](https://python-all.ru/3.16/c-api/threads.html#c.PyEval_RestoreThread)
- [`Py_BEGIN_ALLOW_THREADS`](https://python-all.ru/3.16/c-api/threads.html#c.Py_BEGIN_ALLOW_THREADS) и [`Py_END_ALLOW_THREADS`](https://python-all.ru/3.16/c-api/threads.html#c.Py_END_ALLOW_THREADS)

Эти функции по-прежнему следует использовать в сборке со свободными потоками для управления состоянием потока, даже когда [GIL](https://python-all.ru/3.16/glossary.html#term-GIL) отключён. Например, если вы создаёте поток вне Python, необходимо вызвать [`PyThreadState_Ensure()`](https://python-all.ru/3.16/c-api/threads.html#c.PyThreadState_Ensure) перед вызовом Python API, чтобы убедиться, что поток имеет корректное состояние потока Python.

Следует продолжать вызывать [`PyEval_SaveThread()`](https://python-all.ru/3.16/c-api/threads.html#c.PyEval_SaveThread) или [`Py_BEGIN_ALLOW_THREADS`](https://python-all.ru/3.16/c-api/threads.html#c.Py_BEGIN_ALLOW_THREADS) вокруг блокирующих операций, таких как ввод-вывод или захват блокировок, чтобы позволить другим потокам запустить [циклический сборщик мусора](https://python-all.ru/3.16/glossary.html#term-garbage-collection).

## Защита внутреннего состояния расширения

Ваше расширение может иметь внутреннее состояние, которое ранее защищалось GIL. Возможно, потребуется добавить блокировку для защиты этого состояния. Подход будет зависеть от вашего расширения, но некоторые распространённые шаблоны включают:

- **Кэши**: глобальные кэши – распространённый источник общего состояния. Рассмотрите возможность использования блокировки для защиты кэша или отключите его в сборке со свободными потоками, если кэш не критичен для производительности.
- **Глобальное состояние**: глобальное состояние может потребовать защиты с помощью блокировки или переноса в локальную память потока. В C11 и C++11 для [локальной памяти потока](https://python-all.ru/3.16/howto/free-threading-extensions.html) предусмотрены `thread_local` или `_Thread_local`.

## Критические секции

В сборке со свободной потоковой обработкой CPython предоставляет механизм, называемый «критическими секциями», для защиты данных, которые в противном случае защищались бы GIL. Хотя разработчики расширений могут не взаимодействовать напрямую с внутренней реализацией критических секций, понимание их поведения важно при использовании некоторых функций C API или управлении общим состоянием в сборке со свободной потоковой обработкой.

### Что такое критические секции?

Концептуально критические секции действуют как уровень предотвращения взаимоблокировок, построенный поверх простых мьютексов. Каждый поток поддерживает стек активных критических секций. Когда потоку необходимо захватить блокировку, связанную с критической секцией (например, неявно при вызове потокобезопасной функции C API, такой как [`PyDict_SetItem()`](https://python-all.ru/3.16/c-api/dict.html#c.PyDict_SetItem), или явно с помощью макросов), он пытается захватить лежащий в основе мьютекс.

### Использование критических секций

Основные API для работы с критическими секциями:

- [`Py_BEGIN_CRITICAL_SECTION`](https://python-all.ru/3.16/c-api/synchronization.html#c.Py_BEGIN_CRITICAL_SECTION) и [`Py_END_CRITICAL_SECTION`](https://python-all.ru/3.16/c-api/synchronization.html#c.Py_END_CRITICAL_SECTION) – для блокировки одного объекта
- [`Py_BEGIN_CRITICAL_SECTION2`](https://python-all.ru/3.16/c-api/synchronization.html#c.Py_BEGIN_CRITICAL_SECTION2) и [`Py_END_CRITICAL_SECTION2`](https://python-all.ru/3.16/c-api/synchronization.html#c.Py_END_CRITICAL_SECTION2) – для одновременной блокировки двух объектов

Эти макросы должны использоваться парами и находиться в одной области видимости C, так как они создают новую локальную область. Эти макросы ничего не делают в сборках без свободной потоковой обработки, поэтому их можно безопасно добавлять в код, который должен поддерживать оба типа сборки.

Типичное применение критической секции – блокировка объекта при доступе к его внутреннему атрибуту. Например, если тип расширения имеет внутреннее поле счётчика, можно использовать критическую секцию при чтении или записи этого поля:

```c
// читать счётчик, возвращает новую ссылку на внутреннее значение счётчика
PyObject *result;
Py_BEGIN_CRITICAL_SECTION(obj);
result = Py_NewRef(obj->count);
Py_END_CRITICAL_SECTION();
return result;

// записывать счётчик, потребляет ссылку из new_count
Py_BEGIN_CRITICAL_SECTION(obj);
obj->count = new_count;
Py_END_CRITICAL_SECTION();
```

### Как работают критические секции

В отличие от традиционных блокировок, критические секции не гарантируют исключительный доступ на всём протяжении своего выполнения. Если поток заблокируется, удерживая критическую секцию (например, захватывая другую блокировку или выполняя ввод-вывод), критическая секция временно приостанавливается – все блокировки освобождаются – и затем возобновляется, когда блокирующая операция завершается.

Такое поведение аналогично тому, что происходит с GIL, когда поток выполняет блокирующий вызов. Ключевые отличия:

- Критические секции работают пообъектно, а не глобально
- Критические секции подчиняются стековой дисциплине в рамках каждого потока (макросы «begin» и «end» обеспечивают это, поскольку они должны быть парными и находиться в одной области видимости)
- Критические секции автоматически освобождают и повторно захватывают блокировки вокруг потенциально блокирующих операций

### Предотвращение взаимоблокировок

Критические секции помогают избежать взаимоблокировок двумя способами:

1. Если поток пытается захватить блокировку, уже удерживаемую другим потоком, он сначала приостанавливает все свои активные критические секции, временно освобождая их блокировки
2. После завершения блокирующей операции сначала повторно захватывается только самая верхняя критическая секция

Это означает, что нельзя полагаться на вложенные критические секции для блокировки нескольких объектов одновременно, так как внутренняя критическая секция может приостановить внешние. Вместо этого используйте [`Py_BEGIN_CRITICAL_SECTION2`](https://python-all.ru/3.16/c-api/synchronization.html#c.Py_BEGIN_CRITICAL_SECTION2) для одновременной блокировки двух объектов.

Обратите внимание, что описанные выше блокировки – это только блокировки на основе [`PyMutex`](https://python-all.ru/3.16/c-api/synchronization.html#c.PyMutex). Реализация критических секций не знает о других механизмах блокировки, которые могут использоваться (например, мьютексах POSIX), и не влияет на них. Также учтите, что блокирование на любом `PyMutex` приводит к приостановке критических секций, но освобождаются только мьютексы, входящие в состав критических секций. Если `PyMutex` используется без критической секции, он не будет освобождён и, следовательно, не получит того же предотвращения взаимоблокировок.

### Важные замечания

- Критические секции могут временно освобождать свои блокировки, позволяя другим потокам изменять защищённые данные. Будьте осторожны с предположениями о состоянии данных после операций, которые могут блокироваться.
- Поскольку блокировки могут временно освобождаться (приостанавливаться), вход в критическую секцию не гарантирует исключительного доступа к защищённому ресурсу на всём её протяжении. Если код внутри критической секции вызывает другую функцию, которая блокируется (например, захватывает другую блокировку, выполняет блокирующий ввод-вывод), все блокировки, удерживаемые потоком через критические секции, будут освобождены. Это аналогично тому, как GIL может освобождаться во время блокирующих вызовов.
- В любой момент гарантированно удерживается только блокировка (или блокировки), связанная с самой последней (верхней) критической секцией. Блокировки внешних вложенных критических секций могли быть приостановлены.
- С помощью этих API можно блокировать не более двух объектов одновременно. Если потребуется заблокировать больше объектов, придётся перестроить код.
- Хотя критические секции не приводят к взаимоблокировке при попытке заблокировать один и тот же объект дважды, для такого случая они менее эффективны, чем специально разработанные рекурсивные блокировки.
- При использовании [`Py_BEGIN_CRITICAL_SECTION2`](https://python-all.ru/3.16/c-api/synchronization.html#c.Py_BEGIN_CRITICAL_SECTION2) порядок объектов не влияет на корректность (реализация сама предотвращает взаимоблокировки), но рекомендуется всегда блокировать объекты в одном и том же порядке.
- Помните, что макросы критических секций предназначены в первую очередь для защиты доступа к *объектам Python*, которые могут участвовать во внутренних операциях CPython, подверженных описанным выше сценариям взаимоблокировок. Для защиты чисто внутреннего состояния расширения более подходящими могут оказаться стандартные мьютексы или другие примитивы синхронизации.

### Пообъектные блокировки (`ob_mutex`)

В сборке со свободной многопоточностью каждый объект Python содержит поле [`ob_mutex`](https://python-all.ru/3.16/c-api/structures.html#c.PyObject.ob_mutex) типа [`PyMutex`](https://python-all.ru/3.16/c-api/synchronization.html#c.PyMutex). Этот мьютекс **зарезервирован для использования API критических секций** ([`Py_BEGIN_CRITICAL_SECTION`](https://python-all.ru/3.16/c-api/synchronization.html#c.Py_BEGIN_CRITICAL_SECTION) / [`Py_END_CRITICAL_SECTION`](https://python-all.ru/3.16/c-api/synchronization.html#c.Py_END_CRITICAL_SECTION)).

> **Предупреждение**
>
> **Не** блокируйте `ob_mutex` напрямую с помощью `PyMutex_Lock(&obj->ob_mutex)`. Смешивание прямых вызовов `PyMutex_Lock` с API критических секций на одном мьютексе может привести к взаимоблокировкам.

Даже если ваш собственный код никогда не использует критические секции для определенного типа объекта, **внутренние компоненты CPython могут использовать API критических секций для любого объекта Python**.

Если ваш тип расширения требует собственной блокировки, добавьте отдельное поле [`PyMutex`](https://python-all.ru/3.16/c-api/synchronization.html#c.PyMutex) (или другой примитив синхронизации) в вашу структуру объекта. `PyMutex` очень легковесный, поэтому стоимость добавления дополнительного поля незначительна.

## Сборка расширений для сборки со свободной многопоточностью

Расширения C API необходимо собирать специально для сборки со свободной многопоточностью. Колеса (wheels), разделяемые библиотеки и двоичные файлы обозначаются суффиксом `t`.

- [pypa/manylinux](https://python-all.ru/3.16/howto/free-threading-extensions.html) поддерживает сборку со свободной многопоточностью с суффиксом `t`, например `python3.14t`.
- [pypa/cibuildwheel](https://python-all.ru/3.16/howto/free-threading-extensions.html) поддерживает сборку колес для сборки со свободной многопоточностью Python 3.14 и новее.

### Ограниченный C API и стабильный ABI

Сборка со свободной многопоточностью в настоящее время не поддерживает [Ограниченный C API](https://python-all.ru/3.16/c-api/stable.html#limited-c-api) или стабильный ABI. Если вы используете [setuptools](https://python-all.ru/3.16/howto/free-threading-extensions.html) для сборки вашего расширения и в настоящее время устанавливаете `py_limited_api=True`, вы можете использовать `py_limited_api=not sysconfig.get_config_var("Py_GIL_DISABLED")`, чтобы отказаться от ограниченного API при сборке со свободной многопоточностью.

> **Примечание**
>
> Вам потребуется собрать отдельные колеса специально для сборки со свободной многопоточностью. Если вы в настоящее время используете стабильный ABI, вы можете продолжать собирать одно колесо для нескольких версий Python без свободной многопоточности.

### Windows

Из-за ограничения официального установщика Windows вам потребуется вручную определить `Py_GIL_DISABLED=1` при сборке расширений из исходных кодов.

> **См. также**
>
> [Портирование модулей расширения для поддержки свободной многопоточности](https://python-all.ru/3.16/howto/free-threading-extensions.html): Руководство по портированию, поддерживаемое сообществом, для авторов расширений.
