从 C++ 函数调用 python lambda 时处理 GIL

Jon*_*rin 10 python gil pybind11

问题

\n

PyGILState_Ensure()pybind11 是否以某种方式神奇地完成了和的工作PyGILState_Release()?如果没有,我该怎么做?

\n

更多细节

\n

关于使用 pybind11 将 python 函数作为回调传递给 C++有 很多 问题,但我还没有找到一个解释如何使用 pybind11 的 GIL 的问题

\n

文档中关于 GIL 的说明非常清楚

\n
\n

[...] 但是,当从 C 创建线程时(例如由具有自己的线程管理的第三方库),它们不\xe2\x80\x99t 保存 GIL,也不存在它们的线程状态结构。

\n

如果您需要从这些线程调用Python代码(通常这将是上述第三方库提供的回调API的一部分),您必须首先通过创建线程状态数据结构向解释器注册这些线程,然后获取GIL,最后存储它们的线程状态指针,然后才能开始使用 Python/C API。

\n
\n

我可以轻松绑定一个需要回调的 C++ 函数:

\n
py::class_<SomeApi> some_api(m, "SomeApi"); \nsome_api\n    .def(py::init<>())\n    .def("mode", &SomeApi::subscribe_mode, "Subscribe to \'mode\' updates.");\n
Run Code Online (Sandbox Code Playgroud)\n

相应的 C++ 函数类似于:

\n
void subscribe_mode(const std::function<void(Mode mode)>& mode_callback);\n
Run Code Online (Sandbox Code Playgroud)\n

但是因为 pybind11 无法了解我的 C++ 实现中发生的线程,所以我认为它无法为我处理 GIL。因此,如果mode_callback由从 C++ 创建的线程调用,这是否意味着我应该为每个调用编写一个包装SomeApi::subscribe_mode器?PyGILState_Ensure()PyGILState_Release()

\n

这个答案似乎做了类似的事情,但仍然略有不同:调用回调时不是“获取 GIL”,而是在启动/停止线程时“释放 GIL”。我仍然想知道是否存在类似的东西py::call_guard<py::gil_scoped_acquire>()可以完全满足我(相信我)的需要,即用PyGILState_Ensure()and包装我的回调PyGILState_Release()

\n

Dus*_*zza 9

一般来说

pybind11 尝试做正确的事情,当 pybind11 知道它正在调用 python 函数时,或者在通过 pybind11 从 python 调用的 C++ 代码中,GIL 将被保留。使用 pybind11 时,唯一需要显式获取 GIL 的情况是,当您编写访问 python 的 C++ 代码并将从其他 C++ 代码调用时,或者您已显式删除 GIL 时。

std::函数包装器

包装器总是在调用函数时std::function通过获取GIL ,因此无论从哪个线程调用它,您的Python回调将始终在持有GIL的情况下被调用。gil_scoped_acquire

如果gil_scoped_acquire从当前没有与其关联的 GIL 线程状态的线程调用,那么它将创建一个新的线程状态。作为副作用,如果线程中没有其他东西获取线程状态并增加引用计数,那么一旦您的函数退出,GIL 将由 的析构函数释放gil_scoped_acquire,然后它将删除与该线程关联的线程状态

如果您只从另一个线程调用该函数一次,那么这不是问题。如果您经常调用回调,它将大量创建/删除线程状态,这可能对性能不利。最好在线程启动时创建线程状态(或者更简单,从 Python 启动线程并从 python 调用 C++ 代码)。

  • 是的!做了一个 PR 将其中一些添加到 [pybind11 文档](https://github.com/pybind/pybind11/pull/4057) (4认同)