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, поэтому этот ответ может быть полезен другим.

Другие вопросы по тегам