Python C API - это потокобезопасность?
У меня есть расширение C, которое вызывается из моего многопоточного приложения Python. Я использую статическую переменную i
где-то в функции C, и у меня есть несколько i++
Позже утверждения, которые могут быть запущены из разных потоков Python (эта переменная используется только в моем C-коде, хотя я не передаю ее Python).
По какой-то причине я до сих пор не встретил ни одного состояния гонки, но мне интересно, если это просто удача...
У меня нет никакого связанного с потоками C-кода (нет Py_BEGIN_ALLOW_THREADS или чего-либо еще).
Я знаю, что GIL гарантирует, что инструкции с одиночным байт-кодом являются атомарными и поточно-ориентированными, поэтому i+=1
в Python не являются потокобезопасными.
Но я не знаю о i++
инструкция по расширению C. Любая помощь?
2 ответа
Python не будет выпускать GIL, когда вы запускаете код на C (если вы не скажете это). Он освобождает GIL только перед инструкцией байт-кода (не во время), и с точки зрения интерпретатора выполнение функции C является частью выполнения CALL_FUNCTION
байткод. *
(К сожалению, я не могу найти ссылку на этот параграф в настоящее время, но я почти уверен, что это правильно)
Поэтому, если вы не сделаете что-то конкретное, ваш код на C будет единственным выполняемым потоком, и поэтому любая выполняемая в нем операция должна быть поточно-ориентированной.
Если вы специально хотите выпустить GIL - например, потому что вы выполняете длительные вычисления, которые не мешают Python, не читают из файла или не спят, ожидая чего-то еще, - тогда самый простой способ - это сделать Py_BEGIN_ALLOW_THREADS
затем Py_END_ALLOW_THREADS
когда ты хочешь вернуть это. Во время этого блока вы не можете использовать большинство функций Python API, и вы несете ответственность за обеспечение безопасности потоков в C. Самый простой способ сделать это - использовать только локальные переменные, а не читать или записывать какие-либо глобальные состояния.
Если у вас уже есть поток C, работающий без GIL (поток A), то просто удержание GIL в потоке B не гарантирует, что поток A не изменит глобальные переменные C. Чтобы быть в безопасности, вы должны убедиться, что вы никогда не изменяете глобальное состояние без какого-либо механизма блокировки (либо Python GIL, либо механизма C) во всех ваших функциях C.
Дополнительная мысль
*
Одно место, где GIL может быть освобожден в коде C, - это если код C вызывает что-то, что вызывает выполнение кода Python. Это может быть через использование PyObject_Call
, Менее очевидное место было бы, если Py_DECREF
вызвал выполнение деструктора. Вы вернете GIL к тому времени, когда ваш код на C возобновится, но вы больше не сможете гарантировать неизменность глобальных объектов. Это очевидное не влияет на простые C как x++
,
В шаблонах расширений Python C есть отличный раздел, посвященный безопасности потоков .
Да, это старый пост, но он появился первым в моем поиске в Google, поэтому этот ответ может быть полезен другим.