2014-09-26 75 views
12

我正在玩Python的C API,但很難理解一些特殊情況。我可以測試它,但它似乎是一個容易出錯和耗時的問題。所以我來這裏看看是否有人已經這樣做了。Python多線程多語言解釋器C API

現在的問題是,這是用子解釋器管理多線程的正確方法,線程和子解釋器之間沒有直接關係?

Py_Initialize(); 
PyEval_InitThreads(); /* <-- needed? */ 
_main = PyEval_SaveThread(); /* <-- acquire lock? does it matter? */ 
/* maybe do I not need it? */ 
i1 = Py_NewInterpreter(); 
i2 = Py_NewInterpreter(); 

我使用互斥鎖嗎?需要使用鎖嗎?螺紋功能應該是類似以下內容:(螺紋是非蟒蛇,大概POSIX線程)

線程1

_save = PyThreadState_Swap(i1); 
    // python work 
PyThreadState_Restore(_save); 

線程2(幾乎相同)

_save = PyThreadState_Swap(i1); 
    // python work 
PyThreadState_Restore(_save); 

主題3(幾乎相同,但與子翻譯i2

_save = PyThreadState_Swap(i2); 
    // python work 
PyThreadState_Restore(_save); 

這是正確的嗎?這是我想達到的一般情況嗎?有沒有比賽條件?

謝謝!

回答

14

Python中的子解釋器沒有很好的文檔記錄,甚至沒有很好的支持。以下是對我未知的最好的。它在實踐中似乎運作良好。

在Python中處理線程和子解釋器時,Threre是理解兩個重要的概念。首先,Python解釋器並不是真正的多線程。它有一個全球解釋器鎖(GIL),需要獲取它來執行幾乎所有的Python操作(這個規則有少數例外)。

其次,線程和子解釋器的每個組合都必須有它自己的線程狀態。解釋器爲它管理的每個線程創建一個線程狀態,但是如果你想從非解釋器創建的線程使用Python,則需要創建一個新的線程狀態。

首先,你需要創建子解釋:

初始化的Python

Py_Initialize(); 

初始化Python的線程支持

,如果你打算從多個線程調用Python需要)。這個電話也獲得了GIL。

PyEval_InitThreads(); 

保存當前線程狀態

我可以使用PyEval_SaveThread(),但其副作用之一釋放GIL,然後需要重新獲取。

PyThreadState* _main = PyThreadState_Get(); 

創建子解釋

PyThreadState* ts1 = Py_NewInterpreter(); 
PyThreadState* ts2 = Py_NewInterpreter(); 

恢復主解釋線程狀態

PyThreadState_Swap(_main); 

我們現在有子口譯兩個紗線的狀態。這些線程狀態僅在創建它們的線程中有效。每個想要使用其中一個子解釋器的線程都需要爲該線程和解釋器的組合創建一個線程狀態。

使用從一個新的線程

這裏的子解釋爲在不通過副解釋創建新線程使用子解釋的示例代碼。新線程必須獲取GIL,爲線程創建新的線程狀態並解釋組合,並使其成爲當前的線程狀態。最後必須做相反的事情來清理。

void do_stuff_in_thread(PyInterpreterState* interp) 
{ 
    // acquire the GIL 
    PyEval_AcquireLock(); 

    // create a new thread state for the the sub interpreter interp 
    PyThreadState* ts = PyThreadState_New(ts1->interp); 

    // make ts the current thread state 
    PyThreadState_Swap(ts); 

    // at this point: 
    // 1. You have the GIL 
    // 2. You have the right thread state - a new thread state (this thread was not created by python) in the context of interp 

    // PYTHON WORK HERE 

    // release ts 
    PyThreadState_Swap(NULL); 

    // clear and delete ts 
    PyThreadState_Clear(ts); 
    PyThreadState_Delete(ts); 

    // release the GIL 
    PyEval_ReleaseLock(); 
} 

現在,每個線程都可以做到以下幾點:

線程1

do_stuff_in_thread(ts1->interp); 

線程2

do_stuff_in_thread(ts1->interp); 

Thread3

do_stuff_in_thread(ts2->interp); 

調用Py_Finalize()銷燬所有的子解釋器。或者,可以手動銷燬。這需要在主線程中完成,使用創建子解釋器時創建的線程狀態。最後讓主解釋器線程聲明當前狀態。

// make ts1 the current thread state 
PyThreadState_Swap(ts1); 
// destroy the interpreter 
Py_EndInterpreter(ts1); 

// make ts2 the current thread state 
PyThreadState_Swap(ts2); 
// destroy the interpreter 
Py_EndInterpreter(ts2); 

// restore the main interpreter thread state 
PyThreadState_Swap(_main); 

我希望這可以讓事情更清楚一點。

我有一個用C++編寫的小型完整示例github

+0

謝謝!這正是我所尋找的,「詳細的快速啓動」 - 比快速詳細:)。如果它不適合你,我肯定會錯過「線程和解釋器組合的線程狀態」。 – MariusSiuram 2014-10-27 13:54:40

+2

所以總結一下:有沒有辦法使用真正並行的多個python解釋器實例(多核上的硬件線程)? – japedo 2015-07-08 13:50:56

+1

正確。 Python使用全局解釋器鎖,它允許一次只允許一個線程運行實際的Python代碼。但是,執行長操作的C代碼通常會釋放鎖,直到它返回到Python,以便可以執行另一個線程。這意味着實際使用將取決於您的代碼。 – sterin 2015-07-10 01:30:48