为什么在这种情况下 PyGILState_Release(…) 会出现段错误?

4 投票
2 回答
2181 浏览
提问于 2025-04-16 12:39

我正在为PyAudio实现异步音频播放。它的后台Portaudio通过创建自己的线程来实现异步播放,每当需要或有新的音频数据时,就会调用一个C语言的回调函数。每次这个C语言的回调函数被调用时,我会调用一个之前注册的Python函数,用户需要在这个函数中提供音频数据。

因为这个调用是在一个不是由Python创建的线程中进行的,所以文档上说我必须在调用Python之前先调用PyGILState_Ensure(),然后在之后调用PyGILState_Release()。大概是这样的:

int stream_callback(const void *in, void* out, unsigned long frameCount,
                    const PaStreamCallbackTimeInfo *timeInfo,
                    PaStreamCallbackFlags statusFlags, void *userData)
{
    PyGILState_STATE gstate = PyGILState_Ensure();

    /* create some python variables, as used below… */
    py_result = PyObject_CallFunctionObjArgs(py_callback,
                                             py_frameCount,
                                             py_inTime,
                                             py_curTime,
                                             py_outTime,
                                             py_inputData,
                                             NULL);
    /* evaluate py_result, do some audio stuff… */

    PyGILState_Release(gstate);
    return returnVal;
}

但是在执行PyGILState_Release(gstate)时程序崩溃了。这个回调函数被调用的频率非常高,可能每秒几百到几千次。gstate是一个32位的变量,有时被PyGILState_Ensure()设置为1,有时设置为0。只有在它被设置为1时,程序才会崩溃。通常情况下,会先有一个1,然后是两个到四个0

这让我感觉PyGILState_Release(…)的执行时间比实际返回的时间要长,因此在它还在运行的时候就被调用了,可能是这样。

崩溃时,堆栈跟踪看起来是这样的:

#0  0x00007fff88c287b7 in pthread_mutex_lock ()
#1  0x00000001001009a6 in PyThread_release_lock ()
#2  0x00000001002efc82 in stream_callback (in=0x1014a4670, out=0x1014a4670, frameCount=4316612208, timeInfo=0x1014a4850, statusFlags=4297757032, userData=0x38) at _portaudiomodule.c:1554
#3  0x00000001004e3710 in AdaptingOutputOnlyProcess ()
#4  0x00000001004e454b in PaUtil_EndBufferProcessing ()
#5  0x00000001004e9665 in AudioIOProc ()
#6  0x00000001013485d0 in dyld_stub_strlen ()
#7  0x0000000101348194 in dyld_stub_strlen ()
#8  0x0000000101346523 in dyld_stub_strlen ()
#9  0x0000000101345870 in dyld_stub_strlen ()
#10 0x000000010134aceb in AUGenericOutputEntry ()
#11 0x00007fff88aa132d in HP_IOProc::Call ()
#12 0x00007fff88aa10ff in IOA_Device::CallIOProcs ()
#13 0x00007fff88aa0f35 in HP_IOThread::PerformIO ()
#14 0x00007fff88a9ef44 in HP_IOThread::WorkLoop ()
#15 0x00007fff88a9e817 in HP_IOThread::ThreadEntry ()
#16 0x00007fff88a9e745 in CAPThread::Entry ()
#17 0x00007fff88c5c536 in _pthread_start ()
#18 0x00007fff88c5c3e9 in thread_start ()

这对任何人来说有意义吗?

2 个回答

4

我昨天遇到了一个很相似的问题,不过值得注意的是,我只有在多个线程同时尝试运行 PyGILState_Ensure() 的时候,才出现了段错误。

在我的情况下,问题出在我在初始化解释器时(在主线程中)没有调用 PyEval_InitThreads()。需要注意的是,初始化后必须运行 PyEval_ReleaseLock(),否则下次调用 PyGILState_Ensure() 时会导致死锁,因为 PyEval_InitThreads() 会隐式地获取全局解释器锁(GIL)。

希望这能帮到你。

7

我也遇到过完全一样的问题。解决办法是在主线程中调用 PyEval_InitThreads(),确保在任何回调发生之前进行这个操作。

我认为原因如下。当Python解释器第一次启动时,它不会初始化全局解释器锁(GIL),因为大多数Python程序都是单线程的,而GIL的存在会带来一些小的性能损失。所以在没有初始化GIL的情况下,PyGILState_Ensure()PyGILState_Release() 就会在未初始化的数据上工作,导致各种奇怪的崩溃。

通过调用 PyEval_InitThreads(),GIL就会被初始化,这样 PyGILState_Ensure()PyGILState_Release() 就能正常工作。如果GIL已经被初始化,PyEval_InitThreads() 就不会做任何事情,所以可以安全地多次调用。

撰写回答