升级至Boost 1.83.0后Boost::Python对象转换器出现段错误
升级Boost/Python后异步Redis操作触发段错误问题
我有一个C++编写的Python扩展库,其中包含一个通过Boost异步IO从Redis服务器批量获取值的函数redis::connexion::py_mget,代码如下:
python::object redis::connexion::py_mget(python::object keys) { int size = python::extract<int>(keys.attr("__len__")())(); std::vector<python::object> objects(size); std::string error; // 保护Python GIL避免多线程访问 unsigned int todo = size; std::mutex mutex; std::condition_variable condition; std::unique_lock<std::mutex> lock(mutex); for(auto i : xxs::range(size)) { const auto& key = python::extract<redis::key>(keys[i])(); (*this)( [&,i] (std::unique_ptr<xxs::redis::value> value) { std::unique_lock<std::mutex> lock(mutex); /* 这一行触发段错误 */ objects[i] = key.read(std::move(value)); if(!--todo) condition.notify_one(); }, [&] (const std::string& msg) { std::unique_lock<std::mutex> lock(mutex); error = msg; if(!--todo) condition.notify_one(); } ) << redis::get(key.name()); } while(todo) condition.wait(lock); if(!error.empty()) THROW(error); return python::make_list(objects); }
其中key.read的实现如下:
virtual python::object read(std::unique_ptr<xxs::redis::value> value) const override { if(value->is_null()) return python::object(); if(!value->is_blob()) THROW("Invalid value stored in REDIS") auto pos = value->blob().seekg(); xxs::object obj; obj.read(pos); /* 此函数触发段错误 */ return python::object(obj); }
已知情况
- 单独调用
key.read(std::move(value))完全正常,但放入带异步IO和互斥锁的原上下文时触发段错误(core dumped)。 - 在Boost 1.79.0 + Python 3.10 + Ubuntu 22.04 + clang环境下运行无问题,升级到Boost 1.83.0/1.84.0/1.85.0 + Python 3.12 + Ubuntu 24.04后出现该问题。
- Valgrind输出无法解读,尝试在多处添加
PyGILState_STATE gstate; gstate = PyGILState_Ensure(); PyGILState_Release(gstate);仅导致死锁。
问题分析与解决方案
GIL管理逻辑错误
异步回调在Boost IO线程池执行,这些线程不属于Python主线程,必须在操作Python对象前手动获取GIL,操作完成后释放。之前的死锁是因为互斥锁与GIL的获取顺序冲突,正确的做法是拆分操作:- 先获取GIL处理Python对象相关逻辑
- 再锁定互斥锁更新C++侧共享数据
- 严格避免在持有互斥锁时获取GIL
修改后的回调示例:
[&,i] (std::unique_ptr<xxs::redis::value> value) { // 先获取GIL,所有Python对象操作必须在GIL保护下执行 PyGILState_STATE gstate = PyGILState_Ensure(); python::object py_val; try { py_val = key.read(std::move(value)); } catch (...) { PyGILState_Release(gstate); throw; } // 锁定互斥锁更新共享数据 std::unique_lock<std::mutex> lock(mutex); objects[i] = py_val; if(!--todo) condition.notify_one(); // 最后释放GIL PyGILState_Release(gstate); }Boost.Python版本兼容问题
Boost 1.83+对Python 3.12的GIL管理和对象生命周期处理做了严格适配,旧版本的宽松检查被移除,所有涉及python::object的构造、赋值操作必须严格在GIL保护范围内执行。对象生命周期验证
检查value->blob().seekg()返回的流是否会在value被移动销毁后失效,确保obj.read(pos)执行期间,pos指向的内存始终有效。死锁规避
永远不要在持有互斥锁的情况下获取GIL,反之亦然。将GIL保护的Python操作与互斥锁保护的C++共享数据操作完全拆分。
额外检查项
- 确认编译链接的Boost.Python版本与运行时一致,避免Ubuntu 24.04多版本Boost导致的链接混乱。
- 检查
THROW宏是否会在未释放GIL的情况下抛出异常,导致GIL泄漏。 - 尝试用
PyEval_SaveThread()和PyEval_RestoreThread()替代PyGILState_Ensure()/Release(),部分场景下适配性更好。
内容的提问来源于stack exchange,提问作者user195066
相关产品推荐
相关产品推荐

