PyGILState_Ensure()导致死锁
PyGILState_Ensure() Causing 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);
}
我的代码不做任何与线程相关的事情,尽管我已经尝试了许多其他事情。例如,基于此,我尝试调用PyEval_InitThreads()
,但不清楚应该在哪里对扩展进行调用。我把它放在PyMODINIT_FUNC
里。这些尝试都会导致来自Python的死锁、崩溃或神秘的致命错误,例如PyEval_ReleaseThread:错误的线程状态。
这是在带有Python 3.6.1的Linux上。有什么想法可以让这个"简单"的回调工作吗?
可能的过失
我没有意识到在另一个线程中,库处于忙/等待循环中,等待回调的线程。在gdb
中,info threads
使这一点变得明显。我能看到的唯一解决方案是跳过那些对回调的特定调用;考虑到繁忙/等待的循环,我看不出有什么办法能保证他们的安全。在这种情况下,这是可以接受的,这样做可以消除死锁。
此外,在进行任何操作之前,我似乎也需要调用PyEval_InitThreads()
。在C++扩展中,还不清楚它应该去哪里。其中一个回复建议在Python中通过创建和删除一次性threading.Thread
来间接执行此操作。这似乎并没有解决问题,反而触发了一个致命的Python错误:take_gil:NULL tstate,我认为这意味着仍然没有gil。基于这一点和它所指的问题,我的猜测是PyEval_InitThreads()
导致当前线程成为GIL的主线程。如果这个调用是在短暂的一次性线程中进行的,那么这可能是个问题。是的,我只是猜测,如果有人不必解释,我将不胜感激。
此答案仅适用于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 *
根据我对文档的阅读,这应该会强制在导入模块之前初始化线程。然后,您在那里编写的回调函数应该可以工作了。
我不太相信这能起作用,但你的模块初始化函数可以这样做:
if (!PyEval_ThreadsInitialized())
{
PyEval_InitThreads();
}
这应该起作用,因为如果PyEval_ThreadsInitialized()
不是真的,那么模块init函数应该由唯一存在的Python线程执行,并且持有GIL是正确的做法。
这些都是我的猜测。我从来没有做过这样的事,我对你的问题毫无头绪的评论就是明证。但从我对文档的阅读来看,这两种方法都应该有效。
我是StackOverflow的新手,但在过去几天里,我一直致力于在多线程C++系统中嵌入python,并遇到了许多代码自身死锁的情况。以下是我一直用来确保线程安全的解决方案:
class PyContextManager {
private:
static volatile bool python_threads_initialized;
public:
static std::mutex pyContextLock;
PyContextManager(/* if python_threads_initialized is false, call PyEval_InitThreads and set the variable to true */);
~PyContextManager();
};
#define PY_SAFE_CONTEXT(expr)
{
std::unique_lock<std::mutex>(pyContextLock);
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
expr;
PyGILState_Release(gstate);
}
正在初始化.cpp文件中的布尔值和互斥对象。
我注意到,如果没有互斥,PyGILState_Ensure()命令可能会导致线程死锁。同样,在另一个PySafeContext的expr中调用PySafeContext将导致线程在等待其互斥对象时阻塞。
使用这些函数,我相信你的回调函数会是这样的:
void Wrapper::myCallback()
{
PyContextManager cm();
PY_SAFE_CONTEXT(
PyObject *result=PyObject_CallMethod(_pyObj,"callback",nullptr);
if (result) Py_DECREF(result);
);
}
如果您不认为您的代码可能需要对Python进行多线程调用,那么您可以轻松地扩展宏并从类结构中取出静态变量。这就是我处理未知线程的方式,启动并确定它是否需要启动系统,避免重复编写GIL函数的繁琐。
希望这能有所帮助!
我已经在Python中封装了C++观察器。如果您正在使用boost,那么您可以在boost _PYTHON_MODULE:中调用PyEval_InitThreads()
BOOST_PYTHON_MODULE(eapipy)
{
boost::shared_ptr<Python::InitialisePythonGIL> gil(new Python::InitialisePythonGIL());
....
}
然后,我使用一个类来控制从C++回调到Python。
struct PyLockGIL
{
PyLockGIL()
: gstate(PyGILState_Ensure())
{
}
~PyLockGIL()
{
PyGILState_Release(gstate);
}
PyLockGIL(const PyLockGIL&) = delete;
PyLockGIL& operator=(const PyLockGIL&) = delete;
PyGILState_STATE gstate;
};
如果你在任何时间内调用C++,你也可以放弃GIL:
struct PyRelinquishGIL
{
PyRelinquishGIL()
: _thread_state(PyEval_SaveThread())
{
}
~PyRelinquishGIL()
{
PyEval_RestoreThread(_thread_state);
}
PyRelinquishGIL(const PyLockGIL&) = delete;
PyRelinquishGIL& operator=(const PyLockGIL&) = delete;
PyThreadState* _thread_state;
};
我们的代码是多线程的,这种方法运行良好。
- 获取日期异步信号安全吗?如果在信号处理程序中使用,它会导致死锁吗
- 如何在没有死锁和/或争用的情况下正确使用 std::mutex C++?
- 用C++中的std::condition_variable将线程置于死锁中会有风险吗
- 使用 std::async 时死锁,将来作为成员
- 如何调试读写器锁的死锁?
- 为什么在Visual Studio 2013上的std::this_thread::sleep_for上死锁
- localtime() 函数正在调用 ___lll_lock_wait_private(),这会使线程陷入死锁
- 如何重现 Boost 进程文档提示的死锁?
- 多线程Windows GUI应用程序中的死锁
- 为什么printf会导致与future.get的死锁,而cout则不会?
- C++中具有阻塞队列和障碍的死锁
- 死锁使用 std::mutex 来保护多个线程中的 cout
- 避免并发等待对象中的死锁
- 在VC++中从DLLMAIN内部调用D3D的CREATEDEVICE时,它会创建一个死锁(loaderlock?)。有没有办法克服这个问题?最终目标内
- 当用2个螺纹锁定时,将recursive_mutex死锁
- 程序在 C++11 中使用条件变量进入死锁
- 一个线程提升的死锁
- 单个生产者/多个消费者死锁
- 当被调用方法使用调用方已锁定的同一锁时,如何避免死锁
- 如何实现无保留和等待以防止死锁