C++ 应用程序在嵌入 Python 解释器尝试第二次导入外部模块时崩溃。

3
如果我在不同的pybind11::scoped_interpreter会话中两次导入外部模块,应用程序将在eval.h的eval函数中崩溃,位于以下行:
PyObject *result = PyRun_String(buffer.c_str(), start, global.ptr(), local.ptr());

使用

Exception thrown at 0x00007FFD710C4E0C (multiarray.cp36-win_amd64.pyd) in pybind-test.exe: 0xC0000005: Access violation writing location 0x000000000000000A.

可重现的示例代码

namespace py = pybind11;
void test() {
    try {
        py::scoped_interpreter guard{};
        py::object mainScope = py::module::import("__main__").attr("__dict__");
        py::exec(
            "import numpy\n",
            mainScope);
    }
    catch (py::error_already_set const &pythonErr) {  std::cout << pythonErr.what(); }
}
int main() {
    test();   // Runs fine
    test();   // Crashes at py::exec
}

我觉得这与pybind11的embed.h中的注释有关:
引用: 解释器可以通过再次调用initialize_interpreter来重新启动。使用pybind11创建的模块可以安全地重新初始化。但是,Python本身无法完全卸载二进制扩展模块,并且在重新启动解释器方面存在几个注意事项。所有细节都可以在CPython文档中找到。简而言之,由于参考循环或用户创建的全局数据,可能不会释放所有解释器内存。
那么,这就意味着不能调用Python解释器两次吗?我有一个包含帮助numpy函数的Python文件,我需要从C++中的算法执行的不同点调用它。这是否意味着我不能这样做?
2个回答

8

根据在pybind11 github仓库的讨论,建议使用py::initialize_interpreterpy::finalize_interpreter代替py::scoped_interpreter,并在中间调用解释器多次。

需要注意的是:“Python解释器不完全支持线程安全。为了支持多线程的Python程序,有一个全局锁,称为全局解释器锁或GIL”。

例如:

namespace py = pybind11;
void test() {
    try {
        py::object mainScope = py::module::import("__main__").attr("__dict__");
        py::exec(
            "import numpy\n",
            mainScope);
    }
    catch (py::error_already_set const &pythonErr) {  std::cout << pythonErr.what(); }
}
int main() {
   py::initialize_interpreter();
    test();  
    test();   
    py::finalize_interpreter();
}

我知道这是一个相当老的答案,但我刚刚偶然发现它。根据文档(现在找不到链接),test() 应该在自己的作用域内(即将 {} 放在它周围),否则解释器会在 test 中的所有内容完成之前被解构(至少他们有一个例子,在那里他们读回了一个返回值,但在解释器之后被解构)。 - Florian Blume

2

根据这篇文章https://www.boost.org/doc/libs/1_47_0/libs/python/todo.html#pyfinalize-safety,目前最好永远不要直接或通过scoped_interpreter调用PyFinalize。

在调用finalize后重新加载模块并再次初始化时可能会导致错误。我在遵循当前批准的答案https://dev59.com/-azka4cB1Zd3GeqP8Gce#51069948时遇到了这个问题。

https://www.boost.org/doc/libs/1_47_0/libs/python/todo.html#pyfinalize-safety

PyFinalize 安全性:目前 Boost.Python 有几个全局(或函数静态)对象,它们的存在使得引用计数保持不为零,直到卸载 Boost.Python 共享对象。这可能会导致崩溃,因为当引用计数降至零时,就没有解释器了。为了安全地调用 PyFinalize(),我们必须注册一个 atexit 程序,销毁这些对象并释放所有 Python 引用计数,以便 Python 可以在仍然有解释器的情况下清理它们。Dirk Gerrits 已经承诺完成这项工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接