Incorporer un interpréteur Python dans un programme C ++ multi-thread avec pybind11
J'essaie d'utiliser pybind11 afin de faire en sorte qu'une bibliothèque C ++ tierce appelle une méthode Python. La bibliothèque est multithread et chaque thread crée un objet Python, puis effectue de nombreux appels aux méthodes de l'objet.
Mon problème est que l'appel aux py::gil_scoped_acquire acquire;
blocages. Un code minimal qui reproduit le problème est donné ci-dessous. Qu'est-ce que je fais mal?
// main.cpp
class Wrapper
{
public:
Wrapper()
{
py::gil_scoped_acquire acquire;
auto obj = py::module::import("main").attr("PythonClass")();
_get_x = obj.attr("get_x");
_set_x = obj.attr("set_x");
}
int get_x()
{
py::gil_scoped_acquire acquire;
return _get_x().cast<int>();
}
void set_x(int x)
{
py::gil_scoped_acquire acquire;
_set_x(x);
}
private:
py::object _get_x;
py::object _set_x;
};
void thread_func()
{
Wrapper w;
for (int i = 0; i < 10; i++)
{
w.set_x(i);
std::cout << "thread: " << std::this_thread::get_id() << " w.get_x(): " << w.get_x() << std::endl;
std::this_thread::sleep_for(100ms);
}
}
int main() {
py::scoped_interpreter python;
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i)
threads.push_back(std::thread(thread_func));
for (auto& t : threads)
t.join();
return 0;
}
et le code Python:
// main.py
class PythonClass:
def __init__(self):
self._x = 0
def get_x(self):
return self._x
def set_x(self, x):
self._x = x
Des questions connexes peuvent être trouvées ici et ici , mais ne m'ont pas aidé à résoudre le problème.
Réponses
J'ai réussi à résoudre le problème en libérant le GIL dans le thread principal, avant de démarrer les threads de travail (ajouté py::gil_scoped_release release;
). Pour tous ceux qui sont intéressés, ce qui suit fonctionne maintenant (également ajouté le nettoyage des objets Python):
#include <pybind11/embed.h>
#include <iostream>
#include <thread>
#include <chrono>
#include <sstream>
namespace py = pybind11;
using namespace std::chrono_literals;
class Wrapper
{
public:
Wrapper()
{
py::gil_scoped_acquire acquire;
_obj = py::module::import("main").attr("PythonClass")();
_get_x = _obj.attr("get_x");
_set_x = _obj.attr("set_x");
}
~Wrapper()
{
_get_x.release();
_set_x.release();
}
int get_x()
{
py::gil_scoped_acquire acquire;
return _get_x().cast<int>();
}
void set_x(int x)
{
py::gil_scoped_acquire acquire;
_set_x(x);
}
private:
py::object _obj;
py::object _get_x;
py::object _set_x;
};
void thread_func(int iteration)
{
Wrapper w;
for (int i = 0; i < 10; i++)
{
w.set_x(i);
std::stringstream msg;
msg << "iteration: " << iteration << " thread: " << std::this_thread::get_id() << " w.get_x(): " << w.get_x() << std::endl;
std::cout << msg.str();
std::this_thread::sleep_for(100ms);
}
}
int main() {
py::scoped_interpreter python;
py::gil_scoped_release release; // add this to release the GIL
std::vector<std::thread> threads;
for (int i = 0; i < 5; ++i)
threads.push_back(std::thread(thread_func, 1));
for (auto& t : threads)
t.join();
return 0;
}
Python est connu pour avoir un verrou d'interpréteur global .
Vous devez donc essentiellement écrire votre propre interpréteur Python à partir de zéro, ou télécharger le code source de Python et l'améliorer beaucoup.
Si vous êtes sous Linux, vous pouvez envisager d'exécuter de nombreux interpréteurs Python (en utilisant des syscalls appropriés (2) , avec pipe (7) ou unix (7) pour la communication interprocessus ) - peut-être un processus Python communiquant avec chacun de vos threads C ++.
Qu'est-ce que je fais mal?
Coder en Python quelque chose qui devrait être codé autrement. Avez-vous envisagé d'essayer SBCL ?
Certaines bibliothèques (par exemple Tensorflow ) peuvent être appelées à la fois depuis Python et C ++. Peut-être pourriez-vous vous en inspirer ...
En pratique, si vous n'avez qu'une douzaine de threads C ++ sur une puissante machine Linux, vous pourriez vous permettre d'avoir un processus Python par thread C ++. Ainsi, chaque thread C ++ aurait son propre processus Python compagnon.
Sinon, prévoyez plusieurs années de travail pour améliorer le code source de Python afin de supprimer son GIL. Vous pouvez coder votre plugin GCC pour vous aider dans cette tâche - analyser et comprendre le code C de Python.