『Python CoolBook』C擴展庫_其六_線程
GIL操作
想讓C擴展代碼和Python解釋器中的其他進程一起正確的執行, 那麽你就需要去釋放並重新獲取全局解釋器鎖(GIL)。
在Python接口封裝中去釋放並重新獲取全局解釋器鎖(GIL),此時本段程序失去GIL運行,其他線程可以無視本函數的運行而運行,直到Py_END_ALLOW_THREADS:
#include "Python.h" ... PyObject *pyfunc(PyObject *self, PyObject *args) { ... Py_BEGIN_ALLOW_THREADS // Threaded C code. Must not use Python API functions ... Py_END_ALLOW_THREADS ... return result; }
只有當你確保沒有Python C API函數在C中執行的時候你才能安全的釋放GIL。 GIL需要被釋放的常見的場景是在計算密集型代碼中需要在C數組上執行計算(比如在numpy中) 或者是要執行阻塞的I/O操作時(比如在一個文件描述符上讀取或寫入時)。
當GIL被釋放後,其他Python線程才被允許在解釋器中執行。 Py_END_ALLOW_THREADS
宏會阻塞執行直到調用線程重新獲取了GIL。
C和Python中的線程混用
混合使用C、Python和線程, 有些線程是在C中創建的,超出了Python解釋器的控制範圍, 並且一些線程還使用了Python C API中的函數時,需要確保正確的初始化和管理Python的全局解釋器鎖(GIL)。
要想這樣,可以將下列代碼放到你的C代碼中並確保它在任何線程被創建之前被調用:
#include <Python.h> ... if (!PyEval_ThreadsInitialized()) { PyEval_InitThreads(); } ...
對於任何調用Python對象或Python C API的C代碼,確保你首先已經正確地獲取和釋放了GIL, 這可以用 PyGILState_Ensure()
和 PyGILState_Release()
來做到,如下所示:
... /* Make sure we own the GIL */ PyGILState_STATE state = PyGILState_Ensure(); /* Use functions in the interpreter */ ... /* Restore previous GIL state and return */ PyGILState_Release(state); ...
在涉及到C和Python的高級程序中,很多事情一起做是很常見的—— 可能是對C、Python、C線程、Python線程的混合使用。 只要你確保解釋器被正確的初始化,並且涉及到解釋器的C代碼執行了正確的GIL管理,應該沒什麽問題。
要註意的是調用 PyGILState_Ensure()
並不會立刻搶占或中斷解釋器。 如果有其他代碼正在執行,這個函數被中斷知道那個執行代碼釋放掉GIL。 在內部,解釋器會執行周期性的線程切換,因此如果其他線程在執行, 調用者最終還是可以運行的(盡管可能要先等一會)。
『Python CoolBook』C擴展庫_其六_線程