2011-02-28 106 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。

希望這會有所幫助。

相關問題