boost python 线程分段错误
boost python threading segmentation fault
考虑以下简单的 python 扩展。当 start()-ed
时,Foo
只会将下一个连续整数添加到 py::list
,每秒一次:
#include <boost/python.hpp>
#include <thread>
#include <atomic>
namespace py = boost::python;
struct Foo {
Foo() : running(false) { }
~Foo() { stop(); }
void start() {
running = true;
thread = std::thread([this]{
while(running) {
std::cout << py::len(messages) << std::end;
messages.append(py::len(messages));
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
void stop() {
if (running) {
running = false;
thread.join();
}
}
std::thread thread;
py::list messages;
std::atomic<bool> running;
};
BOOST_PYTHON_MODULE(Foo)
{
PyEval_InitThreads();
py::class_<Foo, boost::noncopyable>("Foo",
py::init<>())
.def("start", &Foo::start)
.def("stop", &Foo::stop)
;
}
鉴于上述情况,以下简单的 python 脚本始终出现段错误,甚至从未打印任何内容:
>>> import Foo
>>> f = Foo.Foo()
>>> f.start()
>>> Segmentation fault (core dumped)
核心指向:
namespace boost { namespace python {
inline ssize_t len(object const& obj)
{
ssize_t result = PyObject_Length(obj.ptr());
if (PyErr_Occurred()) throw_error_already_set(); // <==
return result;
}
}} // namespace boost::python
其中:
(gdb) inspect obj
= (const boost::python::api::object &) @0x62d368: {<boost::python::api::object_base> = {<boost::python::api::object_operators<boost::python::api::object>> = {<boost::python::def_visitor<boost::python::api::object>> = {<No data fields>}, <No data fields>}, m_ptr = []}, <No data fields>}
(gdb) inspect obj.ptr()
= []
(gdb) inspect result
= 0
为什么在线程中 运行 时会失败? obj
看起来不错,result
设置正确。为什么 PyErr_Occurred()
会发生?谁设置的?
简而言之,CPython 解释器周围有一个互斥量,称为 Global Interpreter Lock (GIL)。此互斥锁可防止对 Python 对象执行并行操作。因此,在任何时间点,最多允许一个线程(已获得 GIL 的线程)对 Python 个对象执行操作。当存在多个线程时,在不持有 GIL 的情况下调用 Python 代码会导致未定义的行为。
C 或 C++ 线程有时在 Python 文档中称为外来线程。 Python 解释器没有能力控制外来线程。因此,外来线程负责管理 GIL 以允许与 Python 线程并发或并行执行。考虑到这一点,让我们检查一下原始代码:
while (running) {
std::cout << py::len(messages) << std::endl; // Python
messages.append(py::len(messages)); // Python
std::this_thread::sleep_for(std::chrono::seconds(1)); // No Python
}
如上所述,当线程拥有 GIL 时,线程主体中的三行中只有两行需要 运行。处理此问题的一种常见方法是使用 RAII classes 来帮助管理 GIL。例如,下面的gil_lock
class,当一个gil_lock
对象被创建时,调用线程将获得GIL。当 gil_lock
对象被销毁时,它会释放 GIL。
/// @brief RAII class used to lock and unlock the GIL.
class gil_lock
{
public:
gil_lock() { state_ = PyGILState_Ensure(); }
~gil_lock() { PyGILState_Release(state_); }
private:
PyGILState_STATE state_;
};
然后线程主体可以使用显式范围来控制锁的生命周期。
while (running) {
// Acquire GIL while invoking Python code.
{
gil_lock lock;
std::cout << py::len(messages) << std::endl;
messages.append(py::len(messages));
}
// Release GIL, allowing other threads to run Python code while
// this thread sleeps.
std::this_thread::sleep_for(std::chrono::seconds(1));
}
这是一个基于原始代码的完整示例,demonstrates 一旦 GIL 被显式管理,程序就可以正常工作:
#include <thread>
#include <atomic>
#include <iostream>
#include <boost/python.hpp>
/// @brief RAII class used to lock and unlock the GIL.
class gil_lock
{
public:
gil_lock() { state_ = PyGILState_Ensure(); }
~gil_lock() { PyGILState_Release(state_); }
private:
PyGILState_STATE state_;
};
struct foo
{
foo() : running(false) {}
~foo() { stop(); }
void start()
{
namespace python = boost::python;
running = true;
thread = std::thread([this]
{
while (running)
{
{
gil_lock lock; // Acquire GIL.
std::cout << python::len(messages) << std::endl;
messages.append(python::len(messages));
} // Release GIL.
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
void stop()
{
if (running)
{
running = false;
thread.join();
}
}
std::thread thread;
boost::python::list messages;
std::atomic<bool> running;
};
BOOST_PYTHON_MODULE(example)
{
// Force the GIL to be created and initialized. The current caller will
// own the GIL.
PyEval_InitThreads();
namespace python = boost::python;
python::class_<foo, boost::noncopyable>("Foo", python::init<>())
.def("start", &foo::start)
.def("stop", &foo::stop)
;
}
交互使用:
>>> import example
>>> import time
>>> foo = example.Foo()
>>> foo.start()
>>> time.sleep(3)
0
1
2
>>> foo.stop()
>>>
考虑以下简单的 python 扩展。当 start()-ed
时,Foo
只会将下一个连续整数添加到 py::list
,每秒一次:
#include <boost/python.hpp>
#include <thread>
#include <atomic>
namespace py = boost::python;
struct Foo {
Foo() : running(false) { }
~Foo() { stop(); }
void start() {
running = true;
thread = std::thread([this]{
while(running) {
std::cout << py::len(messages) << std::end;
messages.append(py::len(messages));
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
void stop() {
if (running) {
running = false;
thread.join();
}
}
std::thread thread;
py::list messages;
std::atomic<bool> running;
};
BOOST_PYTHON_MODULE(Foo)
{
PyEval_InitThreads();
py::class_<Foo, boost::noncopyable>("Foo",
py::init<>())
.def("start", &Foo::start)
.def("stop", &Foo::stop)
;
}
鉴于上述情况,以下简单的 python 脚本始终出现段错误,甚至从未打印任何内容:
>>> import Foo
>>> f = Foo.Foo()
>>> f.start()
>>> Segmentation fault (core dumped)
核心指向:
namespace boost { namespace python {
inline ssize_t len(object const& obj)
{
ssize_t result = PyObject_Length(obj.ptr());
if (PyErr_Occurred()) throw_error_already_set(); // <==
return result;
}
}} // namespace boost::python
其中:
(gdb) inspect obj
= (const boost::python::api::object &) @0x62d368: {<boost::python::api::object_base> = {<boost::python::api::object_operators<boost::python::api::object>> = {<boost::python::def_visitor<boost::python::api::object>> = {<No data fields>}, <No data fields>}, m_ptr = []}, <No data fields>}
(gdb) inspect obj.ptr()
= []
(gdb) inspect result
= 0
为什么在线程中 运行 时会失败? obj
看起来不错,result
设置正确。为什么 PyErr_Occurred()
会发生?谁设置的?
简而言之,CPython 解释器周围有一个互斥量,称为 Global Interpreter Lock (GIL)。此互斥锁可防止对 Python 对象执行并行操作。因此,在任何时间点,最多允许一个线程(已获得 GIL 的线程)对 Python 个对象执行操作。当存在多个线程时,在不持有 GIL 的情况下调用 Python 代码会导致未定义的行为。
C 或 C++ 线程有时在 Python 文档中称为外来线程。 Python 解释器没有能力控制外来线程。因此,外来线程负责管理 GIL 以允许与 Python 线程并发或并行执行。考虑到这一点,让我们检查一下原始代码:
while (running) {
std::cout << py::len(messages) << std::endl; // Python
messages.append(py::len(messages)); // Python
std::this_thread::sleep_for(std::chrono::seconds(1)); // No Python
}
如上所述,当线程拥有 GIL 时,线程主体中的三行中只有两行需要 运行。处理此问题的一种常见方法是使用 RAII classes 来帮助管理 GIL。例如,下面的gil_lock
class,当一个gil_lock
对象被创建时,调用线程将获得GIL。当 gil_lock
对象被销毁时,它会释放 GIL。
/// @brief RAII class used to lock and unlock the GIL.
class gil_lock
{
public:
gil_lock() { state_ = PyGILState_Ensure(); }
~gil_lock() { PyGILState_Release(state_); }
private:
PyGILState_STATE state_;
};
然后线程主体可以使用显式范围来控制锁的生命周期。
while (running) {
// Acquire GIL while invoking Python code.
{
gil_lock lock;
std::cout << py::len(messages) << std::endl;
messages.append(py::len(messages));
}
// Release GIL, allowing other threads to run Python code while
// this thread sleeps.
std::this_thread::sleep_for(std::chrono::seconds(1));
}
这是一个基于原始代码的完整示例,demonstrates 一旦 GIL 被显式管理,程序就可以正常工作:
#include <thread>
#include <atomic>
#include <iostream>
#include <boost/python.hpp>
/// @brief RAII class used to lock and unlock the GIL.
class gil_lock
{
public:
gil_lock() { state_ = PyGILState_Ensure(); }
~gil_lock() { PyGILState_Release(state_); }
private:
PyGILState_STATE state_;
};
struct foo
{
foo() : running(false) {}
~foo() { stop(); }
void start()
{
namespace python = boost::python;
running = true;
thread = std::thread([this]
{
while (running)
{
{
gil_lock lock; // Acquire GIL.
std::cout << python::len(messages) << std::endl;
messages.append(python::len(messages));
} // Release GIL.
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
void stop()
{
if (running)
{
running = false;
thread.join();
}
}
std::thread thread;
boost::python::list messages;
std::atomic<bool> running;
};
BOOST_PYTHON_MODULE(example)
{
// Force the GIL to be created and initialized. The current caller will
// own the GIL.
PyEval_InitThreads();
namespace python = boost::python;
python::class_<foo, boost::noncopyable>("Foo", python::init<>())
.def("start", &foo::start)
.def("stop", &foo::stop)
;
}
交互使用:
>>> import example
>>> import time
>>> foo = example.Foo()
>>> foo.start()
>>> time.sleep(3)
0
1
2
>>> foo.stop()
>>>