【问题标题】:Python callbacks on different threads & the GIL不同线程和 GIL 上的 Python 回调
【发布时间】: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_EnsurePyGILState_Release,它会在堆栈中有较早的 python 调用时正常工作,但在没有堆栈中的python调用。

有没有办法确定回调是要进行第一个 python 还是连续调用?或者也许是解决这个线程问题的方法?

【问题讨论】:

    标签: python multithreading callback ctypes python-c-api


    【解决方案1】:

    显然我可以使用_PyThreadState_Current,我仍然不能 100% 确定为什么会这样,但确实如此。看来应该是currentThread 而不是!currentThread

    void callback(){
        PyThreadState * currentThread = _PyThreadState_Current;
        if (PyGILState_GetThisThreadState() || !currentThread){
            PyGILState_STATE gstate;
            gstate = PyGILState_Ensure();
            PyRun_SimpleString(....);
            PyGILState_Release(gstate);
        }else{
            PyRun_SimpleString(....);
        }
    

    感谢this post 为我指明了正确的方向。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-05
      • 2012-12-22
      • 1970-01-01
      相关资源
      最近更新 更多