问题
pybind11是不是神奇地完成了PyGILState_Ensure()
和PyGILState_Release()
的工作?如果没有,我该怎么做?
更多详细信息
关于使用pybind11将python函数作为回调传递给C++,有很多问题,但我还没有找到一个解释pybind11使用GIL的问题。
有关GIL:的文档非常清楚
[…]然而,当从C创建线程时(例如,由具有自己线程管理的第三方库创建),它们不包含GIL,也没有线程状态结构。
如果您需要从这些线程调用Python代码(通常这将是上述第三方库提供的回调API的一部分),您必须首先通过创建线程状态数据结构向解释器注册这些线程,然后获取GIL,最后存储其线程状态指针,然后才能开始使用Python/C API。
我可以很容易地绑定一个接受回调的C++函数:
py::class_<SomeApi> some_api(m, "SomeApi");
some_api
.def(py::init<>())
.def("mode", &SomeApi::subscribe_mode, "Subscribe to 'mode' updates.");
相应的C++函数类似于:
void subscribe_mode(const std::function<void(Mode mode)>& mode_callback);
但由于pybind11无法知道我的C++实现中发生的线程,我想它无法为我处理GIL。因此,如果mode_callback
是由C++创建的线程调用的,这是否意味着我应该为SomeApi::subscribe_mode
编写一个包装器,每次调用都使用PyGILState_Ensure()
和PyGILState_Release()
?
这个答案似乎在做一些类似的事情,但仍然略有不同:而不是"取GIL";当调用回调时;释放GIL";当启动/停止线程时。不过,我想知道是否存在像py::call_guard<py::gil_scoped_acquire>()
这样的东西,可以完全满足我(相信我)的需求,即用PyGILState_Ensure()
和PyGILState_Release()
包装我的回调。
一般
pybind11试图做正确的事情,当pybind11知道它正在调用python函数时,或者在通过pybind11从python调用的C++代码中,GIL将被保留。使用pybind11时,唯一需要显式获取GIL的时间是在编写访问python的C++代码并将从其他C++代码中调用时,或者在显式删除GIL时。
std::函数包装器
当调用函数时,std::function
的包装器总是通过gil_scoped_acquire
获取GIL,因此无论从哪个线程调用,python回调都将始终保持GIL。
如果从当前没有关联GIL线程状态的线程调用gil_scoped_acquire
,那么它将创建一个新的线程状态。副作用是,如果线程中没有其他任何东西获取线程状态并增加引用计数,那么一旦函数退出,则gil_scoped_acquire
的析构函数将释放GIL,然后它将删除与该线程关联的线程状态。
如果您只从另一个线程调用该函数一次,这不是问题。如果您经常调用回调,它会大量创建/删除线程状态,这可能对性能不太好。最好是在线程启动时创建线程状态(或者更容易的是,从Python启动线程并从Python调用C++代码)。