3

如果我在不同的 pybind11::scoped_interpreter 会话中两次导入外部模块,则应用程序在函数 eval 中的 eval.h 中崩溃,如下行:

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解释器呢?我有一个 python 文件,其中包含我需要在 C++ 算法执行的不同点调用的辅助 numpy 函数。这是否意味着我不能这样做?

4

2 回答 2

6

从pybind11 github repo的讨论中转述。

而不是使用py::scoped_interpreterusepy::initialize_interpreterpy::finalize_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();
}
于 2018-06-27T19:15:05.420 回答
0

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

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

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 已承诺完成这项工作。

于 2021-07-05T22:45:50.783 回答