Многопоточность с Python и C API

У меня есть программа на C++, которая использует C API для использования моей библиотеки Python. И библиотека Python, и код C++ являются многопоточными.

В частности, один поток программы C++ создает экземпляр объекта Python, который наследуется от threading.Thread. Мне нужно, чтобы все мои потоки C++ могли вызывать методы для этого объекта.

С самых первых попыток (я наивно просто создаю экземпляр объекта из основного потока, затем жду некоторое время, затем вызываю метод) я заметил, что выполнение потока Python, связанного с только что созданным объектом, останавливается, как только выполнение возвращается к программе на С++.

Если выполнение остается с Python (например, если я вызываю PyRun_SimpleString("time.sleep(5)");), выполнение потока Python продолжается в фоновом режиме, и все работает нормально, пока ожидание не закончится и выполнение не вернется к C++.

Я явно делаю что-то не так. Что мне нужно сделать, чтобы оба мои C++ и Python были многопоточными и могли хорошо работать друг с другом? У меня нет предыдущего опыта в этой области, поэтому, пожалуйста, не предполагайте ничего!


person Matteo Monti    schedule 12.04.2015    source источник


Ответы (2)


Правильный порядок шагов для выполнения того, что вы пытаетесь сделать:

  • В основной теме:

    1. Initialize Python using Py_Initialize*.
    2. Инициализируйте поддержку многопоточности Python, используя PyEval_InitThreads().
    3. Запустите поток C++.

На данный момент основной поток все еще удерживает GIL.

  • In a C++ thread:
    1. Acquire the GIL using PyGILState_Ensure().
    2. Создайте новый объект потока Python и запустите его.
    3. Освободите GIL, используя PyGILState_Release().
    4. Выспитесь, сделайте что-нибудь полезное или выйдите из темы.

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

  • Back in the main thread:
    1. Release the GIL, enabling threads to run using PyEval_SaveThread()
    2. Прежде чем пытаться использовать другие вызовы Python, повторно получите GIL, используя PyEval_RestoreThread()

Я подозреваю, что вы упускаете последний шаг — высвобождение GIL в основном потоке, что позволяет потоку Python выполняться.

У меня есть небольшой, но полный пример, который делает именно это по этой ссылке.

person sterin    schedule 19.04.2015

Вероятно, вы не разблокируете глобальную блокировку интерпретатора при обратном вызове из python. threading.Thread.

Ну, если вы используете голый C API Python, у вас есть некоторая документация о том, как выпустить/приобрести GIL. Но при использовании C++ я должен предупредить вас, что он может выйти из строя при возникновении каких-либо исключений в вашем коде C++. см. здесь.

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

person Arpegius    schedule 12.04.2015
comment
Извините, но я, кажется, не совсем понимаю это. Вот что я делаю: 1) PyGILState_STATE gstate; gstate = PyGILState_Ensure(); 2) Создаю объект Python, который наследуется от threading.Thread 3) PyGILState_Release(gstate); 4) Засыпает на несколько секунд (в C++) В функции запуска объект должен время от времени что-то печатать, но он делает это, по-видимому, только в течение первых нескольких миллисекунд. Что я делаю не так..? - person Matteo Monti; 13.04.2015