C++呼叫PythonAPI執行緒狀態和全域性直譯器鎖

yangdelong發表於2009-10-15

http://hi.baidu.com/zhouhanqing/blog/item/1b3cd983f0f468b76d8119fc.html

Python 直譯器不是完全執行緒安全的。當前執行緒想要安全訪問 Python 物件的前提是獲取用以支援多執行緒安全的全域性鎖。沒有鎖,甚至多執行緒程式中最簡單的操作都會發生問題。例如,兩個執行緒同時增加一個物件的引用計數,該引用計數可能只增加了一次而非兩次。

因此,存在一個規則:只有獲得了全域性直譯器鎖的執行緒才能操作 Python 物件或者呼叫 Python/C API 函式。為了支援多執行緒 Python 程式設計,直譯器有規律的釋放和回收鎖——預設情況下,每100位元組指令集迴圈一次(可以通過sys.setcheckinterval()設定)。類似檔案讀寫之類的 i/o 片也會隨鎖釋放和回收,這樣其它的執行緒在請求 I/O 操作的執行緒等待I/O操作完成的時候也可以執行。

Python直譯器需要為每個獨立的執行緒保留一些薄記資訊——為此它使用一個稱為PyThreadState的資料結構。然而,這是一個全域性變數:當前 PyThreadState 結構的指標。儘管大多數執行緒包都有辦法儲存“每執行緒全域性資料”,Python 的內建平臺無關執行緒指令還不支援它。因此,必須明確操作當前的執行緒狀態。

大多數境況下這都是很簡單的。全域性直譯器鎖的操作程式碼主要是以下結構:

1 Save the thread state in a local variable.   
     2 Release the interpreter lock.   
     3 ...Do some blocking I/O operation...   
     4 Reacquire the interpreter lock.   
     5 Restore the thread state from the local variable.

This is so common that a pair of macros exists to simplify it:

這種方式如此通用,我們可以用一對現成的巨集來簡化它:

 1 Py_BEGIN_ALLOW_THREADS   
     2 ...Do some blocking I/O operation...   
     3 Py_END_ALLOW_THREADS

The Py_BEGIN_ALLOW_THREADS macro opens a new block and declares a hidden local variable; the Py_END_ALLOW_THREADS macro closes the block. Another advantage of using these two macros is that when Python is compiled without thread support, they are defined empty, thus saving the thread state and lock manipulations.

Py_BEGIN_ALLOW_THREADS 巨集開啟一個新的 block 並且定義一個隱藏的區域性變數;Py_END_ALLOW_THREADS 巨集關閉這個 block 。這兩個巨集還有一個高階的用途:如果 Python 編譯為不支援執行緒的版本,他們定義為空,因此儲存執行緒狀態並鎖定操作。

When thread support is enabled, the block above expands to the following code:

如果支援執行緒,這個 block 就會展開為以下程式碼:

1     PyThreadState *_save;   
     2    
     3     _save = PyEval_SaveThread();   
     4     ...Do some blocking I/O operation...   
     5     PyEval_RestoreThread(_save);

Using even lower level primitives, we can get roughly the same effect as follows:

使用更低階的元素,我們可以獲得同樣的效果:

1     PyThreadState *_save;   
    2    
    3     _save = PyThreadState_Swap(NULL);   
    4     PyEval_ReleaseLock();   
    5     ...Do some blocking I/O operation...   
    6     PyEval_AcquireLock();   
    7     PyThreadState_Swap(_save);

 

這裡有些微妙的不同,細節上,因為鎖操作不保證全域性變數 erron 的一致,PyEval_RestoreThread() 儲存和恢復 errno。同樣,不支援執行緒時,PyEval_SaveThread() PyEval_RestoreThread() 不操作鎖,在這種情況下 PyEval_ReleaseLock() PyEval_AcquireLock() 不可用。這使得不支援執行緒的直譯器可以動態載入支援執行緒的擴充套件。

全域性直譯器鎖用於保護當前執行緒狀態的指標。當事方鎖並儲存狀態的時候,當前執行緒狀態指標必須在鎖釋放之前回收(因為另一個指標將會隨之獲取鎖並且在全域性變數中儲存它自己的執行緒狀態)。相反,獲取鎖並恢復執行緒狀態的時候,鎖必須在儲存狀態指標之前就獲得。

為什麼我要對這些進行詳細介紹?因為從 C 中建立執行緒的時候,它們沒有全域性直譯器鎖,也沒有對應的執行緒狀態資料結構。這些執行緒在他們使用 Python/C API 之前必須自舉,首先要建立執行緒狀態資料結構,然後獲取鎖,最後儲存它們的執行緒狀態指標。完成工作之後,他們可以重置執行緒狀態指標,釋放鎖,最後釋放他們的執行緒資料結構。

2.3版開始,執行緒可以使用 PyGILState_*()函式方便的自動獲取以上的所有功能。從C執行緒中進入 Python 呼叫的典型方法現在變成:

1     PyGILState_STATE gstate;   
    2     gstate = PyGILState_Ensure();   
    3   
    4     /* Perform Python actions here.  */   
    5     result = CallSomeFunction();   
    6     /* evaluate result */   
    7    
    8     /* Release the thread. No Python API allowed beyond this point. */   
    9     PyGILState_Release(gstate);

注意 PyGILState_*() 函式假定只有一個全域性直譯器(由 Py_Initialize() 自動建立)。Python 還支援建立附加的直譯器(通過 Py_NewInterpreter()),但是 PyGILState_*() 不支援混合多直譯器。

1. PyInterpreterState

這個資料結構描述幾個協作執行緒共享的狀態。屬於同一個直譯器的執行緒共享它們的模組維護和幾個其它的內部子項。這個結構沒有公開成員。

屬於不同直譯器的執行緒除了可用記憶體、開啟的檔案描述符之類的程式狀態不共享任何東西。全域性直譯器鎖也由所有執行緒共享,與它們所屬的直譯器無關。

2. PyThreadState

這個資料結構描述了單個執行緒的狀態。唯一的資料成員是 PyInterpreterState *interp,這個執行緒的直譯器狀態。

3. void PyEval_InitThreads( )

初始化和獲取全域性直譯器鎖。它應該在主執行緒中建立,並且應該在第二個執行緒建立或者類似 PyEval_ReleaseLock() PyEval_ReleaseThread(tstate) 之類的執行緒操作之前。它不需要在 PyEval_SaveThread() PyEval_RestoreThread()之前呼叫。

This is a no-op when called for a second time. It is safe to call this function before calling Py_Initialize().

第二次呼叫沒有操作(不做任何事情)。它可以在 Py_Initialize() 被呼叫之前安全呼叫。

只有一個主執行緒的時候,不需要鎖操作。這是通常的情景(大多數 Python 程式設計師不用執行緒),鎖操作稍微拖慢了直譯器。因此,鎖沒有從一開始就建立。這種情況等同於已經獲取了鎖:只有一個執行緒的時候,所有的物件訪問都是安全的。因此,當該函式初始化鎖,它也可以獲得鎖。Python 執行緒模組建立一個新的執行緒之前,它呼叫PyEval_InitThreads(),瞭解有鎖或者還沒有建立鎖。當這個呼叫返回時,它確保鎖以被建立,並且呼叫的執行緒已經得到它。

It is not safe to call this function when it is unknown which thread (if any) currently has the global interpreter lock.

當前擁有全域性直譯器鎖的執行緒(或其它什麼)未知時,呼叫這個函式不安全。

This function is not available when thread support is disabled at compile time.

編譯時如果不支援執行緒,這個函式不可用。

4. int PyEval_ThreadsInitialized( )

如果 PyEval_InitThreads() 已經被呼叫,這個函式返回非0值。因為單執行緒的時候可以不呼叫鎖 API,這個函式可以在沒有獲得鎖的情況下使用。這個函式在編譯時禁用執行緒支援的情況下不可用。2.4版新加入。

5. void PyEval_AcquireLock( )

獲取全域性直譯器鎖。鎖必須提前建立。如果執行緒已經得到鎖,會發生死鎖。這個函式在編譯時禁用執行緒支援的情況下不可用。

6. void PyEval_ReleaseLock( )

釋放全域性直譯器鎖。鎖必須提前建立。這個函式在編譯時禁用執行緒支援的情況下不可用。

7. void PyEval_AcquireThread( PyThreadState *tstate)

獲得全域性直譯器鎖並將當前執行緒狀態設定為 tstate ,它不能為NULL。鎖必須提前建立。如果執行緒已經擁有鎖,會發生死鎖。這個函式在編譯時禁用執行緒支援的情況下不可用。

8. void PyEval_ReleaseThread( PyThreadState *tstate)

重置當前執行緒狀態為NULL並釋放全域性直譯器鎖。鎖必須提前建立並且以在當前執行緒中獲得。引數 tstate 不能為 NULL。它只能用於校驗它描述的當前執行緒狀態——如果它不對,會報告一個致命錯誤。這個函式在編譯時禁用執行緒支援的情況下不可用。

9. PyThreadState* PyEval_SaveThread( )

釋放直譯器鎖(如果它已經被建立而且定義了執行緒支援)並且將執行緒狀態設為 NULL ,返回前一個執行緒狀態(如果它不為 NULL )。如果鎖已經建立,當前執行緒必須獲取它。(這個函式甚至在編譯時不支援執行緒的情況下也能使用)。

10. void PyEval_RestoreThread( PyThreadState *tstate)

獲取直譯器鎖(如果支援執行緒並且鎖已經建立)並設定執行緒狀態為非空的 tstate。如果鎖已經建立,當前執行緒必須沒有在之前獲得它,不然會發生死鎖。(這個函式甚至在編譯時不支援執行緒的情況下也能使用)。

The following macros are normally used without a trailing semicolon; look for example usage in the Python source distribution.

以下的巨集通常呼叫的時候不以分號結尾;可以在釋出的 Python 原始碼中找到使用的示例。

11. Py_BEGIN_ALLOW_THREADS

這個巨集展開為 "{ PyThreadState *_save; _save = PyEval_SaveThread();" 。注意它包含一個左大括號;它必須在其後匹配 Py_END_ALLOW_THREADS 巨集。這個巨集的介紹參見後面。當執行緒支援在編譯時被禁用時它是一個 no-op

12. Py_END_ALLOW_THREADS

This macro expands to "PyEval_RestoreThread(_save); }". Note that it contains a closing brace; it must be matched with an earlier Py_BEGIN_ALLOW_THREADS macro. See above for further discussion of this macro. It is a no-op when thread support is disabled at compile time.

這個巨集展開為 "PyEval_RestoreThread(_save); }" 。注意它包含一個右大括號;它必須在之前匹配一個 Py_BEGIN_ALLOW_THREADS 巨集。這個巨集的介紹參見前面。當執行緒支援在編譯時被禁用時它是一個 no-op

13. Py_BLOCK_THREADS

This macro expands to "PyEval_RestoreThread(_save);": it is equivalent to Py_END_ALLOW_THREADS without the closing brace. It is a no-op when thread support is disabled at compile time.

這個巨集展開為 "PyEval_RestoreThread(_save);" ;它等同於 Py_END_ALLOW_THREADS 去掉右大括號。當執行緒支援在編譯時被禁用時它是一個 no-op

14. Py_UNBLOCK_THREADS

This macro expands to "_save = PyEval_SaveThread();": it is equivalent to Py_BEGIN_ALLOW_THREADS without the opening brace and variable declaration. It is a no-op when thread support is disabled at compile time.

這個巨集展開為 "_save = PyEval_SaveThread();" ;它是等同於 Py_BEGIN_ALLOW_THREADS 去掉左大括號和變數宣告。當執行緒支援在編譯時被禁用時它是一個 no-op

All of the following functions are only available when thread support is enabled at compile time, and must be called only when the interpreter lock has been created.

以下所有函式只能在編譯時確認支援執行緒的情況下可用,並且必須在直譯器鎖建立後被呼叫。

15. PyInterpreterState* PyInterpreterState_New( )

Create a new interpreter state object. The interpreter lock need not be held, but may be held if it is necessary to serialize calls to this function.

建立一個新直譯器狀態物件。不必要捕獲直譯器鎖,但是當需要同步呼叫這個函式進行序列化的時候可能需要鎖定。

16. void PyInterpreterState_Clear( PyInterpreterState *interp)

Reset all information in an interpreter state object. The interpreter lock must be held.

重置直譯器狀態物件中的所有資訊。直譯器鎖必須被獲取。

17. void PyInterpreterState_Delete( PyInterpreterState *interp)

Destroy an interpreter state object. The interpreter lock need not be held. The interpreter state must have been reset with a previous call to PyInterpreterState_Clear().

析構一個直譯器狀態物件。直譯器鎖需要獲取。直譯器物件必須預先用 PyInterpreterState_Clear() 重置。

18. PyThreadState* PyThreadState_New( PyInterpreterState *interp)

Create a new thread state object belonging to the given interpreter object. The interpreter lock need not be held, but may be held if it is necessary to serialize calls to this function.

建立一個從屬於給定直譯器的新執行緒狀態物件。直譯器鎖不需要捕獲,但是需要同步呼叫該函式時可能需要捕獲。

19. void PyThreadState_Clear( PyThreadState *tstate)

Reset all information in a thread state object. The interpreter lock must be held.

重置指定執行緒狀態物件的所有資訊。直譯器鎖必須捕獲。

20. void PyThreadState_Delete( PyThreadState *tstate)

Destroy a thread state object. The interpreter lock need not be held. The thread state must have been reset with a previous call to PyThreadState_Clear().

銷燬一個執行緒狀態物件。不需要捕獲直譯器鎖。執行緒狀態必須提前呼叫 PyThreadState_Clear() 進行清除。

21. PyThreadState* PyThreadState_Get( )

Return the current thread state. The interpreter lock must be held. When the current thread state is NULL, this issues a fatal error (so that the caller needn't check for NULL).

返回當前直譯器狀態。必須捕獲直譯器鎖。當前執行緒狀態如果為 NULL,發生一個致命錯誤(因此呼叫者不需要校驗NULL)。

22. PyThreadState* PyThreadState_Swap( PyThreadState *tstate)

Swap the current thread state with the thread state given by the argument tstate, which may be NULL. The interpreter lock must be held.

將當前執行緒狀態與給定的引數 tstate 交換,tstate可能為 NULL。直譯器鎖必須被捕獲。

23. PyObject* PyThreadState_GetDict( )

Return value: Borrowed reference.

返回值:託管引用。

Return a dictionary in which extensions can store thread-specific state information. Each extension should use a unique key to use to store state in the dictionary. It is okay to call this function when no current thread state is available. If this function returns NULL, no exception has been raised and the caller should assume no current thread state is available. Changed in version 2.3: Previously this could only be called when a current thread is active, and NULL meant that an exception was raised.

返回可儲存執行緒獨立的狀態資訊的一個擴充套件字典。每個擴充套件需要一個唯一鍵用於在字典中儲存狀態。當前執行緒狀態不可用的時候它也可以呼叫。如果這個函式返回 NULL,沒有丟擲異常,呼叫者會假定當前執行緒狀態無效。自 2.3 版以後的修改:以前它只能在當前執行緒啟用的情況下被呼叫,如果返回 NULL 就意味著發生了異常。

24. int PyThreadState_SetAsyncExc( long id, PyObject *exc)

Asynchronously raise an exception in a thread. The id argument is the thread id of the target thread; exc is the exception object to be raised. This function does not steal any references to exc. To prevent naive misuse, you must write your own C extension to call this. Must be called with the GIL held. Returns the number of thread states modified; this is normally one, but will be zero if the thread id isn't found. If exc is NULL, the pending exception (if any) for the thread is cleared. This raises no exceptions. New in version 2.3.

在 執行緒中非同步丟擲一個異常。引數 id 是目標執行緒的執行緒 id exc 是要丟擲的異常物件。這個函式不獲取 exc 的任何引用。為了防止低階錯誤,你必須自己編寫你的 C 擴充套件來呼叫它。呼叫必須捕獲 GIL。返回執行緒狀態修改數;通常為 1 ,但是如果執行緒 id 沒有找到就會返回0。如果 exc NULL,所有異常(任何可能)都會從執行緒中清除。不丟擲異常。2.3版新加入。

25. PyGILState_STATE PyGILState_Ensure( )

Ensure that the current thread is ready to call the Python C API regardless of the current state of Python, or of its thread lock. This may be called as many times as desired by a thread as long as each call is matched with a call to PyGILState_Release(). In general, other thread-related APIs may be used between PyGILState_Ensure() and PyGILState_Release() calls as long as the thread state is restored to its previous state before the Release(). For example, normal usage of the Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS macros is acceptable.

確 保當前執行緒已經可以呼叫與當前 Python 狀態無關的 Python C API,或者它的執行緒鎖。當一個執行緒每次希望匹配到 PyGILState_Release() 呼叫時可能會反覆呼叫這個函式。通常,線上程狀態恢復為 Release() 之前的狀態時,其它執行緒相關的 API 可能會在一對 PyGILState_Ensure() PyGILState_Release() 之間呼叫。例如,通常可以用於Py_BEGIN_ALLOW_THREADS 巨集 Py_END_ALLOW_THREADS

The return value is an opaque "handle" to the thread state when PyGILState_Acquire() was called, and must be passed to PyGILState_Release() to ensure Python is left in the same state. Even though recursive calls are allowed, these handles cannot be shared - each unique call to PyGILState_Ensure must save the handle for its call to PyGILState_Release.

PyGILState_Acquire ()被呼叫的時候,返回值是一個不透明的執行緒狀態“控制程式碼”,Python離開當前狀態時一定會被被傳遞到 PyGILState_Release() 。甚至儘管允許遞迴呼叫,這些控制程式碼也不能共享——每次呼叫 PyGILState_Ensure 都是唯一的,它們的控制程式碼對應它們的PyGILState_Release

When the function returns, the current thread will hold the GIL. Failure is a fatal error. New in version 2.3.

當函式返回,當前執行緒將會捕獲 GIL ,失敗會造成致命錯誤。2.3版新增。

26. void PyGILState_Release( PyGILState_STATE)

Release any resources previously acquired. After this call, Python's state will be the same as it was prior to the corresponding PyGILState_Ensure call (but generally this state will be unknown to the caller, hence the use of the GILState API.)

釋放所有之前獲取的資源。這個呼叫之後,Python的狀態會與之前 PyGILState_Ensure 呼叫一致(但是通常這個狀態對呼叫者是未知的,因此使用 GILState API)。

Every call to PyGILState_Ensure() must be matched by a call to PyGILState_Release() on the same thread. New in version 2.3.

每次呼叫 PyGILState_Ensure() 都要在同一執行緒對應呼叫 PyGILState_Release() 2.3版本新增。

========================================================================

http://blog.csdn.net/liguangyi/archive/2007/06/20/1659697.aspx


一、首先定義一個封裝類,主要是保證PyGILState_Ensure, PyGILState_Release配對使用,而且這個類是可以巢狀使用的。

#include <python.h>

class PyThreadStateLock
{
public:
    PyThreadStateLock(void)
    {
        state = PyGILState_Ensure( );
    }

    ~PyThreadStateLock(void)
    {
         PyGILState_Release( state );
    }
private:
    PyGILState_STATE state;
};


二、在主執行緒中,這樣處理

    // 初始化
    Py_Initialize();
    // 初始化執行緒支援
    PyEval_InitThreads();
    // 啟動子執行緒前執行,為了釋放PyEval_InitThreads獲得的全域性鎖,否則子執行緒可能無法獲取到全域性鎖。
    PyEval_ReleaseThread(PyThreadState_Get());
   
    // 其他的處理,如啟動子執行緒等
    ......
       
    // 保證子執行緒呼叫都結束後
    PyGILState_Ensure();
    Py_Finalize();
    // 之後不能再呼叫任何python的API

三、在主執行緒,或者子執行緒中,呼叫python本身函式的都採用如下處理

    {
        class PyThreadStateLock PyThreadLock;
        // 呼叫python的API函式處理
        ......
    }

另外還有兩個和全域性鎖有關的巨集,Py_BEGIN_ALLOW_THREADS 和 Py_END_ALLOW_THREADS。這兩個巨集是為了在較長時間的C函式呼叫前,臨時釋放全域性鎖,完成後重新獲取全域性鎖,以避免阻塞其他 python的執行緒繼續執行。這兩個巨集可以這樣呼叫

    {
        class PyThreadStateLock PyThreadLock;
        // 呼叫python的API函式處理
        ......

        Py_BEGIN_ALLOW_THREADS
        // 呼叫需要長時間的C函式
        ......
        Py_END_ALLOW_THREADS

        // 呼叫python的API函式處理
        ......
    }

 

========================================================================

相關文章