Einbetten eines Python-Interpreters in ein C ++ - Multithread-Programm mit pybind11
Ich versuche, pybind11 zu verwenden, damit eine C ++ - Bibliothek eines Drittanbieters eine Python-Methode aufruft . Die Bibliothek ist Multithread-fähig, und jeder Thread erstellt ein Python-Objekt und führt dann zahlreiche Aufrufe der Methoden des Objekts durch.
Mein Problem ist, dass der Anruf zu py::gil_scoped_acquire acquire;
Deadlocks. Ein minimaler Code, der das Problem reproduziert, ist unten angegeben. Was mache ich falsch?
// 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;
}
und der Python-Code:
// 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
Verwandte Fragen finden Sie hier und hier , haben mir aber nicht geholfen, das Problem zu lösen.
Antworten
Ich habe es geschafft, das Problem zu lösen, indem ich die GIL im Hauptthread freigegeben habe, bevor ich die Arbeitsthreads gestartet habe (hinzugefügt py::gil_scoped_release release;
). Für alle Interessierten funktioniert jetzt Folgendes (auch das Bereinigen von Python-Objekten wurde hinzugefügt):
#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 verfügt bekanntermaßen über eine globale Interpreter-Sperre .
Sie müssen also grundsätzlich Ihren eigenen Python-Interpreter von Grund auf neu schreiben oder den Quellcode von Python herunterladen und erheblich verbessern.
Wenn Sie unter Linux arbeiten, können Sie viele Python-Interpreter ausführen (unter Verwendung geeigneter Syscalls (2) , mit Pipe (7) oder Unix (7) für die Interprozesskommunikation ) - möglicherweise einen Python-Prozess, der mit jedem Ihrer C ++ - Threads kommuniziert.
Was mache ich falsch?
Codierung in Python etwas, das sonst codiert werden sollte. Haben Sie darüber nachgedacht, SBCL auszuprobieren ?
Einige Bibliotheken (z. B. Tensorflow ) können sowohl von Python als auch von C ++ aufgerufen werden. Vielleicht könnten Sie sich von ihnen inspirieren lassen ...
In der Praxis können Sie es sich leisten, einen Python- Prozess pro C ++ - Thread zu haben, wenn Sie nur ein Dutzend C ++ - Threads auf einem leistungsstarken Linux-Computer haben . Jeder C ++ - Thread hätte also seinen eigenen begleitenden Python-Prozess.
Andernfalls sollten Sie mehrere Jahre Arbeit einplanen, um den Quellcode von Python zu verbessern und dessen GIL zu entfernen. Sie können Ihr GCC-Plugin codieren , um Ihnen bei dieser Aufgabe zu helfen - das Analysieren und Verstehen des C-Codes von Python.