2011-02-28 141 views
3

我正在为PyAudio实施异步音频播放。后端Portaudio通过创建自己的线程并在需要/有新音频数据时调用C回调函数来实现异步回放。每当调用C回调函数时,我都会调用之前注册的Python函数,用户必须提供音频数据。为什么在这种情况下PyGILState_Release(...)段错误?

由于对Python的调用发生在非Python创建的线程中,因此the documentation说我必须在调用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; 
} 

哪些segfaults在PyGILState_Release(gstate)。该回调函数经常被调用非常。就像每秒几百到几千次一样。 gstate是一个32位变量,有时设置为1,有时设置为0PyGILState_Ensure()。它只在设置为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

您链接的问题与PyEval_ReleaseLock有关,永远不会是正确的调用方式(因为它已被弃用),所以这不太可能是您的问题。你可以在调试器中精确地看到PyGILState_Release中发生段错误的位置吗?gstate值为1表示调用来自非Python线程,所以临时线程状态实际上正在被销毁,并且可能会有相当多的事情发生。 – ncoghlan 2011-02-28 13:22:56

+1

另外,你链接到哪个版本? (GIL代码在3.2中改变了一些,所以它使我看到的版本有所不同) – ncoghlan 2011-02-28 13:38:59

+1

我正在使用Python 2.7.1 OSX – bastibe 2011-02-28 13:55:16

回答

4

我有完全相同的问题。解决方法是在任何回调发生之前在主线程上调用PyEval_InitThreads()

我相信原因如下。当Python解释器第一次启动时,它避免了初始化GIL,因为大多数Python程序都是单线程的,GIL的存在会导致一些小的性能损失。因此,如果没有初始化GIL被PyGILState_Ensure()PyGILState_Release()处理未初始化的数据,就会在整个地方造成奇怪的崩溃。

通过调用PyEval_InitThreads() GIL被初始化,并且PyGILState_Ensure()PyGILState_Release()工作正常。如果GIL已经初始化,那么PyEval_InitThreads()什么都不做,因此可以一次又一次地重复呼叫。

3

昨天我遇到了一个与此类似的东西,不过值得注意的是我唯一一次出现分段错误的时候是多线程试图同时运行PyGILState_Ensure()

在我的情况下,这是由于我在初始化解释器(在主线程中)时没有调用PyEval_InitThreads()造成的。请注意,初始化后必须运行PyEval_ReleaseLock(),否则在下一次调用PyGILState_Ensure()时会死锁,因为PyEval_InitThreads()隐式获取GIL。

希望这会有所帮助。

相关问题