Incorporamento di un interprete Python in un programma C ++ multi-thread con pybind11
Sto cercando di utilizzare pybind11 per fare in modo che una libreria C ++ di terze parti chiami un metodo Python. La libreria è multithread e ogni thread crea un oggetto Python, quindi esegue numerose chiamate ai metodi dell'oggetto.
Il mio problema è che la chiamata a py::gil_scoped_acquire acquire;deadlock. Di seguito viene fornito un codice minimo che riproduce il problema. Che cosa sto facendo di sbagliato?
// 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;
}
e il codice 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
Le domande correlate possono essere trovate qui e qui , ma non mi hanno aiutato a risolvere il problema.
Risposte
Sono riuscito a risolvere il problema rilasciando il GIL nel thread principale, prima di avviare i thread di lavoro (aggiunti py::gil_scoped_release release;). Per chiunque sia interessato, ora funziona quanto segue (aggiunto anche la pulizia di oggetti 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 è noto per avere un Global Interpreter Lock .
Quindi fondamentalmente devi scrivere il tuo interprete Python da zero o scaricare il codice sorgente di Python e migliorarlo molto.
Se sei su Linux, potresti prendere in considerazione l'esecuzione di molti interpreti Python (utilizzando le chiamate di sistema appropriate (2) , con pipe (7) o unix (7) per la comunicazione tra processi) - forse un processo Python che comunica con ciascuno dei tuoi thread C ++.
Che cosa sto facendo di sbagliato?
Codificare in Python qualcosa che dovrebbe essere codificato diversamente. Hai considerato di provare SBCL ?
Alcune librerie (ad esempio Tensorflow ) possono essere chiamate sia da Python che da C ++. Forse potresti trarre ispirazione da loro ...
In pratica, se hai solo una dozzina di thread C ++ su una potente macchina Linux, potresti permetterti di avere un processo Python per thread C ++. Quindi ogni thread C ++ avrebbe il proprio processo Python associato.
Altrimenti, budget diversi anni di lavoro per migliorare il codice sorgente di Python per rimuovere il suo GIL. Potresti codificare il tuo plugin GCC per aiutarti in questo compito, analizzando e comprendendo il codice C di Python.