Подтвердить что ты не робот

Вложение python в многопоточное приложение C

Я внедряю интерпретатор python в многопоточное приложение C, и я немного запутался в отношении того, какие API-интерфейсы я должен использовать для обеспечения безопасности потоков.

Из того, что я собрал, при встраивании python, до того, как вызывать любой другой вызов API Python C, перед вложением должен позаботиться о блокировке GIL. Это делается с помощью следующих функций:

gstate = PyGILState_Ensure();
// do some python api calls, run python scripts
PyGILState_Release(gstate);

Но этого, по-видимому, недостаточно. У меня все еще были случайные сбои, так как он, похоже, не предусматривает взаимного исключения для API-интерфейсов Python.

После чтения еще нескольких документов я также добавил:

PyEval_InitThreads();

сразу после вызова Py_IsInitialized(), но там, где возникает запутанная часть. Документы утверждают, что эта функция:

Инициализировать и получить глобальную блокировку интерпретатора

Это говорит о том, что когда эта функция возвращается, предполагается, что GIL заблокирован и должен быть разблокирован каким-то образом. но на практике это, как представляется, не требуется. Благодаря этой линии моя многопоточная работа отлично работала, и взаимное исключение поддерживалось функциями PyGILState_Ensure/Release.
Когда я попытался добавить PyEval_ReleaseLock() после PyEval_ReleaseLock(), приложение быстро блокировалось при последующем вызове PyImport_ExecCodeModule().

Так что мне здесь не хватает?

4b9b3361

Ответ 1

В конце концов я понял это.
После

PyEval_InitThreads();

Вам нужно позвонить

PyEval_SaveThread();

При правильном выпуске GIL для основного потока.

Ответ 2

У меня была точно такая же проблема, и теперь она решена с помощью PyEval_SaveThread() сразу после PyEval_InitThreads(), как вы предлагаете выше. Однако моя фактическая проблема заключалась в том, что я использовал PyEval_InitThreads() после PyInitialise(), который затем заставлял PyGILState_Ensure() блокировать при вызове из разных последующих последовательных потоков. Итак, вот что я делаю сейчас:

  • Существует глобальная переменная:

    static int gil_init = 0; 
    
  • Из основного потока загрузите собственное расширение C и запустите интерпретатор Python:

    Py_Initialize() 
    
  • Из нескольких других потоков мое приложение одновременно делает много вызовов в API Python/C:

    if (!gil_init) {
        gil_init = 1;
        PyEval_InitThreads();
        PyEval_SaveThread();
    }
    state = PyGILState_Ensure();
    // Call Python/C API functions...    
    PyGILState_Release(state);
    
  • Из основного потока остановите интерпретатор Python

    Py_Finalize()
    

Все другие решения, которые я пробовал, либо вызвали случайные сиг-сигналы Python, либо блокировку/блокировку с помощью PyGILState_Ensure().

Документация на Python действительно должна быть более понятной и, по крайней мере, служить примером как для приложений внедрения, так и для расширения.

Ответ 3

Наличие многопоточного приложения C, пытающегося связываться из нескольких потоков с несколькими потоками Python одного экземпляра CPython, выглядит для меня рискованным.

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

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

Другой альтернативой может быть интерпретатор Stackless Python. Это устраняет проблему с GIL, но я не уверен, что вы столкнулись с другими проблемами, связанными с несколькими потоками. stackless была заменой для моего (однопоточного) приложения C.