Spec-Zone.ru › Python 3.14

Состояния потоков и глобальная блокировка интерпретатора

За исключением свободнопоточной сборки CPython, интерпретатор Python обычно не является потокобезопасным. Для поддержки многопоточных программ на Python используется глобальная блокировка, называемая глобальной блокировкой интерпретатора или GIL. Поток должен удерживать её, прежде чем обращаться к объектам Python. Без этой блокировки даже самые простые операции могут вызывать проблемы в многопоточной программе: например, если два потока одновременно увеличивают счётчик ссылок одного и того же объекта, счётчик может увеличиться только на единицу, а не на две.

Таким образом, работать с объектами Python или вызывать C API Python может только поток, удерживающий GIL.

Для имитации параллельного выполнения интерпретатор регулярно пытается переключать потоки между байт-кодными инструкциями (см. sys.setswitchinterval()). Поэтому блокировки необходимы для обеспечения потокобезопасности и в коде на чистом Python.

Кроме того, глобальная блокировка интерпретатора освобождается во время блокирующих операций ввода-вывода, например при чтении или записи файла. В C API это выполняется путём отсоединения состояния потока.

Интерпретатор Python хранит некоторую локальную для потока информацию в структуре данных, называемой PyThreadState, или состоянием потока. У каждого потока есть локальный указатель на PyThreadState; состояние потока, на которое указывает этот указатель, считается присоединённым.

В каждый момент времени у потока может быть только одно присоединённое состояние потока. Обычно присоединённое состояние потока аналогично удержанию GIL, за исключением свободнопоточных сборок. В сборках с включённой GIL присоединение состояния потока блокируется до тех пор, пока не удастся получить GIL. Однако даже в сборках с отключённой GIL требуется иметь присоединённое состояние потока, поскольку интерпретатору необходимо отслеживать, какие потоки могут обращаться к объектам Python.

Примечание

Даже в свободнопоточной сборке присоединение состояния потока может блокироваться: GIL может быть включена повторно или потоки могут быть временно приостановлены (например, во время сборки мусора).

При использовании C API Python обычно всегда имеется присоединённое состояние потока, в том числе при встраивании интерпретатора и реализации методов, поэтому самостоятельно создавать состояние потока обычно не требуется. Состояние потока не присоединено лишь в некоторых случаях, например в блоке Py_BEGIN_ALLOW_THREADS или в новом потоке. Если вы не уверены, проверьте, возвращает ли PyThreadState_GetUnchecked() значение NULL.

Если выяснится, что состояние потока всё же нужно создать, вызовите PyThreadState_New(), а затем PyThreadState_Swap() или воспользуйтесь опасной функцией PyGILState_Ensure().

Отсоединение состояния потока в коде расширения

Большинство кода расширений, манипулирующего состоянием потока, имеет следующую простую структуру:

Save the thread state in a local variable.
... Do some blocking I/O operation ...
Restore the thread state from the local variable.

Это настолько распространённая конструкция, что для её упрощения существует пара макросов:

Py_BEGIN_ALLOW_THREADS
... Do some blocking I/O operation ...
Py_END_ALLOW_THREADS

Макрос Py_BEGIN_ALLOW_THREADS открывает новый блок и объявляет скрытую локальную переменную; макрос Py_END_ALLOW_THREADS закрывает блок.

Приведённый выше блок разворачивается в следующий код:

PyThreadState *_save;

_save = PyEval_SaveThread();
... Do some blocking I/O operation ...
PyEval_RestoreThread(_save);

Вот как работают эти функции:

Наличие присоединённого состояния потока означает, что интерпретатор удерживает GIL. Чтобы отсоединить его, вызывается PyEval_SaveThread(), а результат сохраняется в локальной переменной.

При отсоединении состояния потока GIL освобождается, что позволяет другим потокам присоединиться к интерпретатору и выполнять код, пока текущий поток занят блокирующим вводом-выводом. После завершения операции ввода-вывода старое состояние потока присоединяется повторно вызовом PyEval_RestoreThread(); вызов будет ожидать, пока не удастся получить GIL.

Примечание

Блокирующий ввод-вывод — наиболее распространённый случай использования отсоединения состояния потока, но этот приём также полезно применять при выполнении длительного машинного кода, которому не нужен доступ к объектам Python или C API Python. Например, стандартные модули zlib и hashlib отсоединяют состояние потока при сжатии или хешировании данных.

В свободнопоточной сборке GIL обычно не используется, но состояние потока всё равно необходимо отсоединять, поскольку интерпретатору периодически требуется блокировать все потоки, чтобы получить согласованное представление объектов Python без риска возникновения гонок. Например, в настоящее время CPython ненадолго приостанавливает все потоки во время работы сборщика мусора.

Предупреждение

Отсоединение состояния потока может привести к неожиданному поведению во время финализации интерпретатора. Подробнее см. Предостережения относительно финализации среды выполнения.

API

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

Примечание

Эти макросы по-прежнему необходимы в свободнопоточной сборке для предотвращения взаимных блокировок.

Py_BEGIN_ALLOW_THREADS
Входит в стабильный ABI.

Этот макрос разворачивается в { PyThreadState *_save; _save = PyEval_SaveThread();. Обратите внимание, что он содержит открывающую фигурную скобку; ему должен соответствовать следующий макрос Py_END_ALLOW_THREADS. Дополнительные сведения об этом макросе приведены выше.

Py_END_ALLOW_THREADS
Входит в стабильный ABI.

Этот макрос разворачивается в PyEval_RestoreThread(_save); }. Обратите внимание, что он содержит закрывающую фигурную скобку; ему должен соответствовать предыдущий макрос Py_BEGIN_ALLOW_THREADS. Дополнительные сведения об этом макросе приведены выше.

Py_BLOCK_THREADS
Входит в стабильный ABI.

Этот макрос разворачивается в PyEval_RestoreThread(_save);: он эквивалентен Py_END_ALLOW_THREADS без закрывающей фигурной скобки.

Py_UNBLOCK_THREADS
Входит в стабильный ABI.

Этот макрос разворачивается в _save = PyEval_SaveThread();: он эквивалентен Py_BEGIN_ALLOW_THREADS без открывающей фигурной скобки и объявления переменной.

Потоки, созданные не средствами Python

При создании потоков с помощью специальных API Python (например, модуля threading) состояние потока автоматически связывается с ними. Однако поток, созданный из машинного кода (например, сторонней библиотекой с собственной системой управления потоками), не имеет присоединённого состояния потока.

Если из таких потоков нужно вызывать код Python (часто это требуется в рамках API обратного вызова, предоставляемого упомянутой сторонней библиотекой), сначала необходимо зарегистрировать эти потоки в интерпретаторе, создав и присоединив новое состояние потока.

Самый надёжный способ сделать это — вызвать PyThreadState_New(), а затем PyThreadState_Swap().

Примечание

Для PyThreadState_New требуется аргумент — указатель на нужный интерпретатор; такой указатель можно получить вызовом PyInterpreterState_Get() из кода, в котором был создан поток.

Например:

/* The return value of PyInterpreterState_Get() from the
   function that created this thread. */
PyInterpreterState *interp = thread_data->interp;

/* Create a new thread state for the interpreter. It does not start out
   attached. */
PyThreadState *tstate = PyThreadState_New(interp);

/* Attach the thread state, which will acquire the GIL. */
PyThreadState_Swap(tstate);

/* Perform Python actions here. */
result = CallSomeFunction();
/* evaluate result or handle exception */

/* Destroy the thread state. No Python API allowed beyond this point. */
PyThreadState_Clear(tstate);
PyThreadState_DeleteCurrent();

Предупреждение

Если интерпретатор был финализирован до вызова PyThreadState_Swap, то interp будет висячим указателем!

Устаревший API

Ещё один распространённый способ вызывать код Python из потока, созданного не средствами Python, — использовать PyGILState_Ensure(), а затем вызвать PyGILState_Release().

Эти функции плохо работают, если в процессе Python существует несколько интерпретаторов. Если в текущем потоке ещё ни разу не использовался интерпретатор Python (что часто бывает с потоками, созданными вне Python), PyGILState_Ensure создаст и присоединит состояние потока для «главного» интерпретатора (первого интерпретатора в процессе Python).

Кроме того, во время финализации интерпретатора у этих функций возникают проблемы с потокобезопасностью. Использование PyGILState_Ensure во время финализации, скорее всего, приведёт к аварийному завершению процесса.

Эти функции используются следующим образом:

PyGILState_STATE gstate;
gstate = PyGILState_Ensure();

/* Perform Python actions here. */
result = CallSomeFunction();
/* evaluate result or handle exception */

/* Release the thread. No Python API allowed beyond this point. */
PyGILState_Release(gstate);

Предостережения, связанные с fork()

Ещё один важный аспект работы потоков — их поведение при вызове C-функции fork(). В большинстве систем с fork() после создания процесса с помощью fork остаётся только поток, вызвавший fork. Это напрямую влияет как на порядок работы с блокировками, так и на всё состояние, хранящееся в среде выполнения CPython.

Поскольку остаётся только «текущий» поток, блокировки, удерживаемые другими потоками, никогда не будут освобождены. Python решает эту проблему для os.fork(): блокировки, используемые внутри Python, захватываются до fork и освобождаются после него. Кроме того, в дочернем процессе сбрасываются все объекты Lock. При расширении или встраивании Python нет способа сообщить Python о дополнительных (не относящихся к Python) блокировках, которые нужно захватить до fork или сбросить после него. Для этого придётся использовать возможности ОС, например pthread_atfork(). Кроме того, при расширении или встраивании Python прямой вызов fork() вместо вызова через os.fork() (с последующим возвратом в Python или вызовом из Python) может привести к взаимной блокировке: одна из внутренних блокировок Python может удерживаться потоком, который перестал существовать после fork. Функция PyOS_AfterFork_Child() пытается сбросить необходимые блокировки, но не всегда может это сделать.

Исчезновение всех остальных потоков также означает, что состояние среды выполнения CPython должно быть корректно очищено; это делает os.fork(). Для этого финализируются все остальные объекты PyThreadState, принадлежащие текущему интерпретатору, а также все остальные объекты PyInterpreterState. Поэтому, а также из-за особой природы «главного» интерпретатора, fork() следует вызывать только в главном потоке этого интерпретатора, где изначально была инициализирована глобальная среда выполнения CPython. Единственное исключение — если сразу после этого будет вызвана функция exec().

Высокоуровневые API

Ниже перечислены наиболее часто используемые типы и функции для написания многопоточных расширений на C.

type PyThreadState
Входит в ограниченный API (как непрозрачная структура).

Эта структура данных представляет состояние одного потока. Единственный открытый элемент данных:

PyInterpreterState *interp

Состояние интерпретатора для этого потока.

void PyEval_InitThreads()
Входит в стабильный ABI.

Устаревшая функция, которая ничего не делает.

В Python 3.6 и более ранних версиях эта функция создавала GIL, если она ещё не существовала.

Изменено в версии 3.9: Теперь функция ничего не делает.

Изменено в версии 3.7: Теперь эту функцию вызывает Py_Initialize(), поэтому вызывать её самостоятельно больше не нужно.

Изменено в версии 3.2: Теперь эту функцию нельзя вызывать до Py_Initialize().

Устарела начиная с версии 3.9.

PyThreadState *PyEval_SaveThread()
Входит в стабильный ABI.

Отсоединяет присоединённое состояние потока и возвращает его. После возврата у потока не будет состояния потока.

void PyEval_RestoreThread(PyThreadState *tstate)
Входит в стабильный ABI.

Устанавливает tstate в качестве присоединённого состояния потока. Переданное состояние потока не должно быть присоединено, иначе возникнет взаимная блокировка. После возврата tstate будет присоединено.

Примечание

Вызов этой функции из потока во время финализации среды выполнения приведёт к его зависанию до завершения программы, даже если поток был создан не средствами Python. Подробнее см. Предостережения относительно финализации среды выполнения.

Изменено в версии 3.14: Если функция вызвана во время финализации интерпретатора, текущий поток зависает, а не завершается.

PyThreadState *PyThreadState_Get()
Входит в стабильный ABI.

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

См. также PyThreadState_GetUnchecked().

PyThreadState *PyThreadState_GetUnchecked()

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

Добавлено в версии 3.13: В Python с 3.5 по 3.12 функция была закрытой и называлась _PyThreadState_UncheckedGet().

PyThreadState *PyThreadState_Swap(PyThreadState *tstate)
Входит в стабильный ABI.

Устанавливает tstate в качестве присоединённого состояния потока и возвращает состояние потока, присоединённое до вызова.

Эту функцию безопасно вызывать без присоединённого состояния потока; в этом случае она просто вернёт NULL, указывая на отсутствие предыдущего состояния потока.

См. также

PyEval_ReleaseThread()

Примечание

Как и PyGILState_Ensure(), эта функция приведёт к зависанию потока, если среда выполнения находится в процессе финализации.

API состояния GIL

Следующие функции используют локальное хранилище потока и несовместимы с субинтерпретаторами:

type PyGILState_STATE
Входит в стабильный ABI.

Тип значения, возвращаемого функцией PyGILState_Ensure() и передаваемого функции PyGILState_Release().

enumerator PyGILState_LOCKED

На момент вызова PyGILState_Ensure() GIL уже удерживалась.

enumerator PyGILState_UNLOCKED

На момент вызова PyGILState_Ensure() GIL не удерживалась.

PyGILState_STATE PyGILState_Ensure()
Входит в стабильный ABI.

Гарантирует, что текущий поток готов вызывать C API Python независимо от текущего состояния Python и присоединённого состояния потока. Поток может вызывать эту функцию сколько угодно раз при условии, что каждому вызову соответствует вызов PyGILState_Release(). Как правило, между вызовами PyGILState_Ensure() и PyGILState_Release() можно использовать другие API для работы с потоками, если до вызова Release() состояние потока будет восстановлено до исходного. Например, допустимо обычное использование макросов Py_BEGIN_ALLOW_THREADS и Py_END_ALLOW_THREADS.

Возвращаемое значение — непрозрачный «дескриптор» присоединённого состояния потока на момент вызова PyGILState_Ensure(). Его необходимо передать функции PyGILState_Release(), чтобы вернуть Python в исходное состояние. Хотя рекурсивные вызовы разрешены, эти дескрипторы нельзя использовать совместно: каждый отдельный вызов PyGILState_Ensure() должен сохранить дескриптор для соответствующего вызова PyGILState_Release().

После возврата функции будет иметься присоединённое состояние потока, и поток сможет вызывать произвольный код Python. В случае сбоя возникает критическая ошибка.

Предупреждение

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

Изменено в версии 3.14: Если функция вызвана во время финализации интерпретатора, текущий поток зависает, а не завершается.

void PyGILState_Release(PyGILState_STATE)
Входит в стабильный ABI.

Освобождает все ранее полученные ресурсы. После вызова состояние Python будет таким же, как до соответствующего вызова PyGILState_Ensure() (однако вызывающему коду это состояние обычно неизвестно — отсюда необходимость API GILState).

Каждому вызову PyGILState_Ensure() должен соответствовать вызов PyGILState_Release() в том же потоке.

PyThreadState *PyGILState_GetThisThreadState()
Входит в стабильный ABI.

Возвращает присоединённое состояние потока для этого потока. Может вернуть NULL, если в текущем потоке не использовался API GILState. Обратите внимание: у главного потока всегда есть такое состояние потока, даже если в нём не вызывалась функция автоматической инициализации состояния потока. В основном это вспомогательная диагностическая функция.

Примечание

Эта функция может возвращать значение, отличное от NULL, даже если состояние потока отсоединено. В большинстве случаев предпочтительнее использовать PyThreadState_Get() или PyThreadState_GetUnchecked().

См. также

PyThreadState_Get()

int PyGILState_Check()

Возвращает 1, если текущий поток удерживает GIL, и 0 в противном случае. Эту функцию можно вызывать в любое время из любого потока. Она вернёт 1, только если состояние потока было инициализировано с помощью PyGILState_Ensure(). В основном это вспомогательная диагностическая функция. Например, она может быть полезна в контекстах обратного вызова или функциях выделения памяти: знание о том, что GIL захвачена, позволяет вызывающему коду выполнять критически важные действия или менять поведение.

Примечание

Если в текущем процессе Python когда-либо создавался субинтерпретатор, эта функция всегда будет возвращать 1. В большинстве случаев предпочтительнее использовать PyThreadState_GetUnchecked().

Добавлено в версии 3.4.

Низкоуровневые API

PyThreadState *PyThreadState_New(PyInterpreterState *interp)
Часть стабильного ABI.

Создаёт новый объект состояния потока, принадлежащий заданному объекту интерпретатора. присоединённое состояние потока не требуется.

void PyThreadState_Clear(PyThreadState *tstate)
Часть стабильного ABI.

Сбрасывает всю информацию в объекте состояния потока. tstate должен быть присоединён

Изменено в версии 3.9: Теперь эта функция вызывает обратный вызов PyThreadState.on_delete. Ранее он вызывался в PyThreadState_Delete().

Изменено в версии 3.13: Обратный вызов PyThreadState.on_delete был удалён.

void PyThreadState_Delete(PyThreadState *tstate)
Часть стабильного ABI.

Уничтожает объект состояния потока. tstate не должен быть присоединён к какому-либо потоку. Перед этим tstate должен быть сброшен предыдущим вызовом PyThreadState_Clear().

void PyThreadState_DeleteCurrent(void)

Отсоединяет присоединённое состояние потока (которое должно быть сброшено предыдущим вызовом PyThreadState_Clear()), а затем уничтожает его.

После возврата ни одно состояние потока не будет присоединено.

PyFrameObject *PyThreadState_GetFrame(PyThreadState *tstate)
Часть стабильного ABI начиная с версии 3.10.

Возвращает текущий кадр состояния потока Python tstate.

Возвращает сильную ссылку. Возвращает NULL, если в данный момент не выполняется ни один кадр.

См. также PyEval_GetFrame().

tstate не должен быть NULL и должен быть присоединён.

Добавлено в версии 3.9.

uint64_t PyThreadState_GetID(PyThreadState *tstate)
Часть стабильного ABI начиная с версии 3.10.

Получает уникальный идентификатор состояния потока состояния потока Python tstate.

tstate не должен быть NULL и должен быть присоединён.

Добавлено в версии 3.9.

PyInterpreterState *PyThreadState_GetInterpreter(PyThreadState *tstate)
Часть стабильного ABI начиная с версии 3.10.

Получает интерпретатор состояния потока Python tstate.

tstate не должен быть NULL и должен быть присоединён.

Добавлено в версии 3.9.

void PyThreadState_EnterTracing(PyThreadState *tstate)

Приостанавливает трассировку и профилирование в состоянии потока Python tstate.

Возобновить их можно с помощью функции PyThreadState_LeaveTracing().

Добавлено в версии 3.11.

void PyThreadState_LeaveTracing(PyThreadState *tstate)

Возобновляет трассировку и профилирование в состоянии потока Python tstate, приостановленные функцией PyThreadState_EnterTracing().

См. также функции PyEval_SetTrace() и PyEval_SetProfile().

Добавлено в версии 3.11.

int PyUnstable_ThreadState_SetStackProtection(PyThreadState *tstate, void *stack_start_addr, size_t stack_size)
Это нестабильный API. Он может измениться без предупреждения в промежуточных выпусках.

Задаёт начальный адрес защиты стека и размер защиты стека для состояния потока Python.

В случае успеха возвращает 0. В случае ошибки устанавливает исключение и возвращает -1.

CPython реализует контроль рекурсии для кода на C, вызывая исключение RecursionError, когда обнаруживает, что стек выполнения машины близок к переполнению. Например, см. функцию Py_EnterRecursiveCall(). Для этого необходимо знать расположение стека текущего потока; обычно эту информацию CPython получает от операционной системы. Если стек изменяется, например, с помощью методов переключения контекста, таких как boost::context из библиотеки Boost, необходимо вызвать PyUnstable_ThreadState_SetStackProtection(), чтобы сообщить CPython об изменении.

Вызовите PyUnstable_ThreadState_SetStackProtection() до или после изменения стека. Между вызовом функции и изменением стека не вызывайте никакие другие API Python на C.

Чтобы отменить эту операцию, см. PyUnstable_ThreadState_ResetStackProtection().

Добавлено в версии 3.15.

void PyUnstable_ThreadState_ResetStackProtection(PyThreadState *tstate)
Это нестабильный API. Он может измениться без предупреждения в промежуточных выпусках.

Сбрасывает начальный адрес защиты стека и размер защиты стека состояния потока Python к значениям по умолчанию, установленным операционной системой.

Объяснение см. в PyUnstable_ThreadState_SetStackProtection().

Добавлено в версии 3.15.

PyObject *PyThreadState_GetDict()
Возвращаемое значение: заимствованная ссылка. Часть стабильного ABI.

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

void PyEval_AcquireThread(PyThreadState *tstate)
Часть стабильного ABI.

Присоединяет tstate к текущему потоку, который не должен быть NULL или уже присоединён.

Вызывающий поток не должен уже иметь присоединённое состояние потока.

Примечание

Вызов этой функции из потока во время завершения работы среды выполнения приведёт к зависанию потока до завершения программы, даже если этот поток был создан не Python. Дополнительные сведения см. в разделе Предостережения при завершении работы среды выполнения.

Изменено в версии 3.8: Поведение приведено в соответствие с функциями PyEval_RestoreThread(), Py_END_ALLOW_THREADS() и PyGILState_Ensure(); вызов во время завершения работы интерпретатора приводит к остановке текущего потока.

Изменено в версии 3.14: Если функция вызвана во время завершения работы интерпретатора, текущий поток зависает, а не останавливается.

PyEval_RestoreThread() — это функция более высокого уровня, которая доступна всегда (даже если потоки не были инициализированы).

void PyEval_ReleaseThread(PyThreadState *tstate)
Часть стабильного ABI.

Отсоединяет присоединённое состояние потока. Аргумент tstate, который не должен быть NULL, используется только для проверки того, что он представляет собой присоединённое состояние потока; в противном случае сообщается о фатальной ошибке.

PyEval_SaveThread() — это функция более высокого уровня, которая доступна всегда (даже если потоки не были инициализированы).

© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/c-api/threads.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API