【发布时间】:2016-11-08 13:31:30
【问题描述】:
我将属于多线程 C 框架的 C 函数包装在 python 模块中。在这个框架中,当某些事件被触发时会执行回调。但是,回调并不总是从同一个线程执行。
与我的问题相关的回调在 python ctypes 中定义。普通 ctypes 回调的问题在于它们尝试使用 python C-API 获取 GIL。当从未调用 Py_initialize() 的线程运行时,此获取失败。
由于框架的原因,我无法更改回调的执行方式,我唯一能做的就是更改回调函数本身。
为了能够控制获取 GIL 的方式,我创建了一个 C 回调函数,它包装了 python 回调函数:
void* callback(void*){
if (PyGILState_GetThisThreadState()) {
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
PyRun_SimpleString(...);
PyGILState_Release(gstate);
} else {
PyRun_SimpleString(...);
}
}
如果 PyGILState_GetThisThreadState() 函数返回非 NULL 值,则表示回调是从调用 Py_initialize() 的同一线程执行的。这意味着可以使用 PyGILState API。这工作正常。
else 部分在从其他线程运行回调时执行。如果它是该线程堆栈上唯一的python调用,它将被正确执行,但是当堆栈中有未完成的python调用时,它将导致分段错误:
#0 0x0000003c46ce6cdb in PyImport_GetModuleDict () from /usr/lib64/libpython2.6.so.1.0
#1 0x0000003c46ce6d2b in PyImport_AddModule () from /usr/lib64/libpython2.6.so.1.0`
#2 0x0000003c46cf2fe8 in PyRun_SimpleStringFlags () from /usr/lib64/libpython2.6.so.1.0
...
<framework calls>
...
#19 0x0000003c46cf1daa in PyRun_StringFlags () from /usr/lib64/libpython2.6.so.1.0
#20 0x0000003c46cf3010 in PyRun_SimpleStringFlags () from /usr/lib64/libpython2.6.so.1.0
#21 0x00007fc72c78dbb6 in execString () from ...
我也尝试将 PyGILState API 用于 else 部分,但它会导致相反的情况:连续 python 调用的回调成功,但当它是堆栈中的第一个 python 调用时导致死锁:
#0 0x0000003c8de0da00 in sem_wait () from /lib64/libpthread.so.0
#1 0x0000003c46cfd428 in PyThread_acquire_lock () from /usr/lib64/libpython2.6.so.1.0
#2 0x0000003c46cd7784 in PyEval_RestoreThread () from /usr/lib64/libpython2.6.so.1.0
#3 0x0000003c46cf0f58 in PyGILState_Ensure () from /usr/lib64/libpython2.6.so.1.0
如果我以与 if(PyGILState_GetThisThreadState()){..} 部分相同的方式获得 GIL 状态,使用 PyGILState_Ensure 和 PyGILState_Release,它会在堆栈中有较早的 python 调用时正常工作,但在没有堆栈中的python调用。
有没有办法确定回调是要进行第一个 python 还是连续调用?或者也许是解决这个线程问题的方法?
【问题讨论】:
标签: python multithreading callback ctypes python-c-api