使用 pybind11 在多线程 C++ 程序中嵌入 Python 解释器
Embedding a Python interpreter in a multi-threaded C++ program with pybind11
我正在尝试使用 pybind11 以使第 3 方 C++ 库调用 Python 方法。该库是多线程的,每个线程创建一个 Python 对象,然后多次调用该对象的方法。
我的问题是调用 py::gil_scoped_acquire acquire;
死锁。下面给出了重现该问题的最小代码。我做错了什么?
// 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;
}
和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
可以找到相关问题here and here,但没有帮我解决问题。
Python is known to have a Global Interpreter Lock.
所以你基本上需要从头开始编写自己的Python解释器,或者下载Python的源代码并对其进行大量改进。
如果你在 Linux,你可以考虑 运行 多个 Python 解释器(使用适当的 syscalls(2), with pipe(7) or unix(7) for interprocess communication)- 也许一个 Python 进程与您的每个 C++ 线程。
What am I doing wrong?
在 Python 中编码应该以其他方式编码的内容。您考虑过 SBCL 吗?
某些库(例如 Tensorflow)可以从 Python 和 C++ 中调用。也许你可以从他们那里得到灵感...
实际上,如果您在一台功能强大的 Linux 机器上只有十几个 C++ 线程,您可以负担得起每个 C++ 线程一个 Python process。所以每个 C++ 线程都会有自己的伴侣 Python 进程。
否则,预算数年的工作来改进 Python 的源代码以删除其 GIL。您可以编写 GCC plugin 代码来帮助您完成该任务 - 分析和理解 Python.
的 C 代码
我设法通过在启动工作线程之前释放主线程中的 GIL 来解决问题(已添加 py::gil_scoped_release release;
)。对于任何感兴趣的人,以下内容现在有效(还添加了清理 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;
}
我正在尝试使用 pybind11 以使第 3 方 C++ 库调用 Python 方法。该库是多线程的,每个线程创建一个 Python 对象,然后多次调用该对象的方法。
我的问题是调用 py::gil_scoped_acquire acquire;
死锁。下面给出了重现该问题的最小代码。我做错了什么?
// 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;
}
和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
可以找到相关问题here and here,但没有帮我解决问题。
Python is known to have a Global Interpreter Lock.
所以你基本上需要从头开始编写自己的Python解释器,或者下载Python的源代码并对其进行大量改进。
如果你在 Linux,你可以考虑 运行 多个 Python 解释器(使用适当的 syscalls(2), with pipe(7) or unix(7) for interprocess communication)- 也许一个 Python 进程与您的每个 C++ 线程。
What am I doing wrong?
在 Python 中编码应该以其他方式编码的内容。您考虑过 SBCL 吗?
某些库(例如 Tensorflow)可以从 Python 和 C++ 中调用。也许你可以从他们那里得到灵感...
实际上,如果您在一台功能强大的 Linux 机器上只有十几个 C++ 线程,您可以负担得起每个 C++ 线程一个 Python process。所以每个 C++ 线程都会有自己的伴侣 Python 进程。
否则,预算数年的工作来改进 Python 的源代码以删除其 GIL。您可以编写 GCC plugin 代码来帮助您完成该任务 - 分析和理解 Python.
的 C 代码我设法通过在启动工作线程之前释放主线程中的 GIL 来解决问题(已添加 py::gil_scoped_release release;
)。对于任何感兴趣的人,以下内容现在有效(还添加了清理 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;
}