Osadzanie interpretera Pythona w wielowątkowym programie C ++ za pomocą pybind11
Próbuję użyć pybind11 , aby wywołać metodę Pythona z biblioteki C ++ innej firmy. Biblioteka jest wielowątkowa, a każdy wątek tworzy obiekt w języku Python, a następnie wykonuje liczne wywołania metod obiektu.
Mój problem polega na tym, że wywołanie py::gil_scoped_acquire acquire;
zakleszczenia. Poniżej podano minimalny kod, który odtwarza problem. Co ja robię źle?
// 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;
}
i kod Pythona:
// 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
Powiązane pytania można znaleźć tutaj i tutaj , ale nie pomogły mi one w rozwiązaniu problemu.
Odpowiedzi
Udało mi się rozwiązać ten problem, zwalniając GIL w głównym wątku, przed uruchomieniem wątków roboczych (dodane py::gil_scoped_release release;
). Dla każdego, kto jest zainteresowany, teraz działa (dodano również czyszczenie obiektów Pythona):
#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;
}
Wiadomo, że Python ma globalną blokadę interpretera .
Więc w zasadzie musisz napisać od podstaw własny interpreter Pythona lub pobrać kod źródłowy Pythona i znacznie go ulepszyć.
Jeśli korzystasz z Linuksa, możesz rozważyć uruchomienie wielu interpreterów Pythona (używając odpowiednich wywołań systemowych (2) , z potokiem (7) lub unixem (7) do komunikacji międzyprocesowej ) - być może jeden proces Pythona komunikujący się z każdym z wątków C ++.
Co ja robię źle?
Kodowanie w Pythonie czegoś, co powinno być zakodowane w inny sposób. Czy rozważałeś wypróbowanie SBCL ?
Niektóre biblioteki (np. Tensorflow ) można wywołać zarówno z języka Python, jak i C ++. Może mógłbyś czerpać z nich inspirację ...
W praktyce, jeśli masz tylko tuzin wątków C ++ na potężnej maszynie z Linuksem, możesz sobie pozwolić na jeden proces Pythona na wątek C ++. Zatem każdy wątek C ++ miałby swój własny towarzyszący proces w Pythonie.
W przeciwnym razie poświęć kilka lat pracy na ulepszenie kodu źródłowego Pythona w celu usunięcia jego GIL. Możesz zakodować swoją wtyczkę GCC, aby pomóc Ci w tym zadaniu - analizować i rozumieć kod C Pythona.