python因为GIL锁的存在,所以在多线程的访问c++中的python解释器时,会出现各种坑
出于业务需求,需要使用python解释器处理前端发过来的python代码,最开始的处理方法是构造了一个单例的python解释器类(我司大佬设计的),在构造函数中调用 Py_Initialize()在析构函数中调用Py_Finalize()(也是大佬用这种方式解决GIL问题的),同时使用try catch捕捉py::error_already_set,在catch中先后调用Py_Finalize()和Py_Initialize()去刷一下状态(这里是我这个菜狗写的)。
但是,在使用过程中发现,如果一直执行正确的python代码,服务就不会有问题,python抛出异常也可以被正确捕捉。但是即使进行了Py_Finalize()和Py_Initialize(),在下一次请求调用时,再去import我们自己封的类,就会提示某个成员已经被注册过了,(也可能在import的时候线程阻塞,此时如果还有请求进来,进程就会段错误,服务500
根据 pybind11官方文档
#include <pybind11/embed.h> // everything needed for embedding
namespace py = pybind11;
int main() {
py::scoped_interpreter guard{}; // start the interpreter and keep it alive
py::print("Hello, World!"); // use the Python API
}
发现官方使用了一个叫scoped_interpreter 的东西,这个可以初始化解释器,直到它的生命周期结束,但是是一个单线程的程序,我在rest接口的回调函数里使用了这个方法,希望每次请求都可以初始化一个python解释器,请求完毕再释放掉。但是在第二次请求时会抛runtime error
最后根据 这篇文章
在主进程里初始化解释器,并且释放GIL锁
int main(int argc, char* argv[]) {
pybind11::scoped_interpreter python;
pybind11::gil_scoped_release release; // add this to release the GIL
...
return 0;
}
然后在我的回调函数中
void callBack(const string &code, string &errorMsg) {
std::mutex pythonMutex;
std::lock_guard<std::mutex> lock(pythonMutex); //线程锁
py::gil_scoped_acquire acquire; //申请GIL锁
py::exec("globals().clear()"); //因为所有用户共用一个解释器,所以清一下变量
try {
py::exec(code); //执行用户提交的代码
} catch (const py::error_already_set &e) {
errorMsg += e.what();
}
}
就好了