PyGILState_Ensure()导致死锁

Jim*_*Jim 6 c++ python multithreading deadlock

我正在用C++编写一个Python扩展,包装一个我无法控制的第三方库.该库创建了一个Python一无所知的线程,并从该线程调用我提供给库的C++回调.我希望该回调调用Python函数,但我使用从文档中读取的方法得到了死锁.这是我对这些的解释.

void Wrapper::myCallback()
{
   PyGILState_STATE gstate=PyGILState_Ensure();
   PyObject *result=PyObject_CallMethod(_pyObj,"callback",nullptr);
   if (result) Py_DECREF(result);
   PyGILState_Release(gstate);
}
Run Code Online (Sandbox Code Playgroud)

我的代码没有做任何与线程相关的事情,尽管我已经尝试了许多其他的东西.基于,例如,我尝试调用PyEval_InitThreads(),但是对于扩展应该在何处进行调用并不明显.我把它放进去了PyMODINIT_FUNC.这些尝试都会导致Python死锁,崩溃或神秘的致命错误,例如PyEval_ReleaseThread:错误的线程状态.

这是在Linux上使用Python 3.6.1.任何想法如何让这个"简单"的回调工作?

可能是罪魁祸首

我没有意识到在另一个线程中,库在忙/等待循环中等待回调的线程.在gdb,info threads使这显而易见.我能看到的唯一解决方案是跳过对回调的特定调用; 鉴于忙/等待循环,我没有看到让它们安全的方法.在这种情况下,这是可以接受的,这样做可以消除死锁.

此外,似乎我还需要PyEval_InitThreads()在此之前调用.在C++扩展中,不清楚应该去哪里.其中一个回复建议通过创建和删除一次性来间接地在Python中进行threading.Thread.这似乎没有解决它,而是触发一个致命的Python错误:take_gil:NULL tstate,我认为这意味着仍然没有GIL.我的猜测,基于这个和它引用的问题,是PyEval_InitThreads()导致当前线程成为GIL的主线程.如果在短暂的一次性线程中进行该调用,可能这是一个问题.是的,我只是在猜测,并希望得到一个没有必要的人的解释.

Omn*_*ous 3

此答案仅适用于 Python >= 3.0.0。我不知道它是否适用于早期的 Python。

将 C++ 模块包装在 Python 模块中,如下所示:

import threading
t = threading.Thread(target=lambda: None, daemon=True)
t.run()
del t
from your_cpp_module import *
Run Code Online (Sandbox Code Playgroud)

根据我对文档的阅读,这应该强制在导入模块之前初始化线程。然后您在那里编写的回调函数应该可以工作。

我对此工作不太有信心,但你的模块初始化函数可以这样做:

if (!PyEval_ThreadsInitialized())
{
    PyEval_InitThreads();
}
Run Code Online (Sandbox Code Playgroud)

这应该可以工作,因为如果不成立的话,你的模块 init 函数应该由唯一存在的 Python 线程执行PyEval_ThreadsInitialized(),并且持有 GIL 是正确的做法。

这些是我的猜测。我从来没有做过这样的事情,我对你的问题的无知评论就证明了这一点。但从我对文档的阅读来看,这两种方法都应该有效。