为什么在这种情况下 PyGILState_Release(…) 会出现段错误?
我正在为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 个回答
我昨天遇到了一个很相似的问题,不过值得注意的是,我只有在多个线程同时尝试运行 PyGILState_Ensure()
的时候,才出现了段错误。
在我的情况下,问题出在我在初始化解释器时(在主线程中)没有调用 PyEval_InitThreads()
。需要注意的是,初始化后必须运行 PyEval_ReleaseLock()
,否则下次调用 PyGILState_Ensure()
时会导致死锁,因为 PyEval_InitThreads()
会隐式地获取全局解释器锁(GIL)。
希望这能帮到你。
我也遇到过完全一样的问题。解决办法是在主线程中调用 PyEval_InitThreads()
,确保在任何回调发生之前进行这个操作。
我认为原因如下。当Python解释器第一次启动时,它不会初始化全局解释器锁(GIL),因为大多数Python程序都是单线程的,而GIL的存在会带来一些小的性能损失。所以在没有初始化GIL的情况下,PyGILState_Ensure()
和 PyGILState_Release()
就会在未初始化的数据上工作,导致各种奇怪的崩溃。
通过调用 PyEval_InitThreads()
,GIL就会被初始化,这样 PyGILState_Ensure()
和 PyGILState_Release()
就能正常工作。如果GIL已经被初始化,PyEval_InitThreads()
就不会做任何事情,所以可以安全地多次调用。