【发布时间】:2018-04-20 09:43:08
【问题描述】:
我正在用 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);
}
我的代码没有做其他与线程相关的事情,尽管我已经尝试了许多其他的事情。例如,基于this,我尝试调用PyEval_InitThreads(),但是对于分机应该在哪里进行调用并不明显。我把它放在PyMODINIT_FUNC。这些尝试都导致了 Python 的死锁、崩溃或神秘的致命错误,例如,PyEval_ReleaseThread: wrong thread state。
这是在带有 Python 3.6.1 的 Linux 上。有什么想法可以让这个“简单”回调工作吗?
可能的罪魁祸首
我没有意识到在另一个线程中,库处于忙/等待循环中,等待回调线程。在gdb、info threads 中,这一点显而易见。我能看到的唯一解决方案是跳过那些对回调的特定调用;鉴于繁忙/等待循环,我看不到让它们安全的方法。在这种情况下,这是可以接受的,这样做可以消除死锁。
此外,在此之前,我似乎还需要致电PyEval_InitThreads()。在 C++ 扩展中,不清楚应该去哪里。其中一个回复建议通过创建和删除一次性threading.Thread 间接在 Python 中执行此操作。这似乎并没有解决它,而是触发了 致命的 Python 错误:take_gil: NULL tstate,我认为这意味着仍然没有 GIL。我的猜测是,基于this 及其所指的问题,PyEval_InitThreads() 导致当前线程成为 GIL 的主线程。如果该调用是在短暂的一次性线程中进行的,那么这可能是个问题。是的,我只是猜测,并希望得到不需要的人的解释。
【问题讨论】:
-
从线程中执行此操作 Python 一无所知,这不是一种非常有效的方法。部分原因是 Python 开始对其控制的线程进行非常严格的管理,以确保一次只有一个线程在运行。
-
我建议有一个队列,其中一个端点是一个 Python 线程,它位于一个紧密的循环中,并在队列中抓取一个信号量,其中包含一些东西。然后它从队列中拉取东西并执行回调函数。
-
哪个版本的 Python?
-
这是在 Linux 上使用 Python 3.6.1。基于this,我得出结论 Python 3 可以使用它没有创建的线程。
-
你说得对,从
PyGILState_Ensure的文档中可以清楚地看出,它正是为这种情况而设计的,因为 Python 使用它没有创建的线程。
标签: python c++ multithreading deadlock