如何正確區(qū)分Python線程
在Python語言中Python線程可以從這里開始與主線程對GIL的競爭,在t_bootstrap中,申請完了GIL,也就是說子線程也就獲得了GIL,使其始終保存著活動線程的狀態(tài)對象。
當(dāng)PyEval_AcquireThread結(jié)束之后,子線程也就獲得了GIL,并且做好了一切執(zhí)行的準(zhǔn)備。接下來子線程通過PyEval_ CallObjectWithKeywords,將最終調(diào)用我們已經(jīng)非常熟悉的PyEval_EvalFrameEx。
也就是Python的字節(jié)碼執(zhí)行引擎。傳遞進(jìn)PyEval_CallObjectWithKeywords的boot->func是一PyFunctionObject對象,正是therad1.py中定義的threadProc編譯后的結(jié)果。在PyEval_CallObjectWithKeywords結(jié)束之后,子線程將釋放GIL,并完成銷毀線程的所有掃尾工作,到了這里,子線程就結(jié)束了。
從t_bootstrap的代碼看上去,似乎子線程會一直執(zhí)行,直到子線程的所有計(jì)算都完成,才會通過PyThreadState_DeleteCurrent釋放GIL。如此一來,那主線程豈非一直都會處于等待GIL的狀態(tài)?如果真是這樣,那Python線程顯然就不可能支持多線程機(jī)制了。
實(shí)際上在PyEval_EvalFrameEx中,圖15-2中顯示的Python內(nèi)部維護(hù)的那個模擬時鐘中斷會不斷地激活線程的調(diào)度機(jī)制,在子線程和主線程之間不斷地進(jìn)行切換。從而真正實(shí)現(xiàn)多線程機(jī)制,當(dāng)然,這一點(diǎn)我們將在后面詳細(xì)剖析?,F(xiàn)在我們感興趣的是子線程在PyEval_AcquireThreade中到底做了什么。
到這里,了解了PyEval_AcquireThread,似乎創(chuàng)建線程的機(jī)制都清晰了。但實(shí)際上,有一個非常重要的機(jī)制——線程狀態(tài)保護(hù)機(jī)制——隱藏在了一個毫不起眼的地方:PyThreadState_New。
- [threadmodule.c]
 - static PyObject* thread_PyThread_start_new_thread(PyObject *self, PyObject
 - *fargs)
 - {
 - PyObject *func, *args, *keyw = NULL;
 - struct bootstate *boot;
 - long ident;
 - PyArg_UnpackTuple(fargs, "start_new_thread", 2, 3, &func, &args, &keyw);
 - //[1]:創(chuàng)建bootstate結(jié)構(gòu)
 - boot = PyMem_NEW(struct bootstate, 1);
 - boot->interp = PyThreadState_GET()->interp;
 - boot->funcfunc = func;
 - boot->argsargs = args;
 - boot->keywkeyw = keyw;
 - //[2]:初始化多線程環(huán)境
 - PyEval_InitThreads(); /* Start the interpreter's thread-awareness */
 - //[3]:創(chuàng)建線程
 - ident = PyThread_start_new_thread(t_bootstrap, (void*) boot);
 - return PyInt_FromLong(ident);
 - [thread.c]
 - /* Support for runtime thread stack size tuning.
 - A value of 0 means using the platform's default stack size
 - or the size specified by the THREAD_STACK_SIZE macro. */
 - static size_t _pythread_stacksize = 0;
 - [thread_nt.h]
 - long PyThread_start_new_thread(void (*func)(void *), void *arg)
 - {
 - unsigned long rv;
 - callobj obj;
 - obj.id = -1; /* guilty until proved innocent */
 - obj.func = func;
 - obj.arg = arg;
 - obj.done = CreateSemaphore(NULL, 0, 1, NULL);
 - rv = _beginthread(bootstrap, _pythread_stacksize, &obj); /* use default stack size */
 - if (rv == (unsigned long)-1) {
 - //創(chuàng)建raw thread失敗
 - obj.id = -1;
 - }
 - else {
 - WaitForSingleObject(obj.done, INFINITE);
 - }
 - CloseHandle((HANDLE)obj.done);
 - return obj.id;
 - }
 
這個機(jī)制對于理解Python線程的創(chuàng)建和維護(hù)是非常關(guān)鍵的。要剖析線程狀態(tài)的保護(hù)機(jī)制,我們首先需要回顧一下線程狀態(tài)。在Python中,每一個Python線程都會有一個線程狀態(tài)對象與之關(guān)聯(lián)。
在線程狀態(tài)對象中,記錄了每一個線程所獨(dú)有的一些信息。實(shí)際上,在剖析Python的初始化過程時,我們曾經(jīng)見過這個對象。每一個線程對應(yīng)的線程狀態(tài)對象都保存著這個線程當(dāng)前的PyFrameObject對象,線程的id這樣一些信息。有時候,線程是需要訪問這些信息的。
比如考慮一個最簡單的情形,在某種情況下,每個線程都需要訪問線程狀態(tài)對象中所保存的thread_id信息,顯然,線程A獲得的應(yīng)該是A的thread_id,線程B亦然。倘若線程A獲得的是B的thread_id,那就壞菜了。這就意味著Python線程內(nèi)部必須有一套機(jī)制,這套機(jī)制與操作系統(tǒng)管理進(jìn)程的機(jī)制非常類似。
我們知道,在操作系統(tǒng)從進(jìn)程A切換到進(jìn)程B時,首先會保存進(jìn)程A的上下文環(huán)境,再進(jìn)行切換;當(dāng)從進(jìn)程B切換回進(jìn)程A時,又會恢復(fù)進(jìn)程A的上下文環(huán)境,這樣就保證了進(jìn)程A始終是在屬于自己的上下文環(huán)境中運(yùn)行。
這里的線程狀態(tài)對象就等同于進(jìn)程的上下文,Python同樣會有一套存儲、恢復(fù)線程狀態(tài)對象的機(jī)制。同時,在Python內(nèi)部,維護(hù)著一個全局變量:PyThreadState * _PyThread- State_Current。
當(dāng)前活動線程所對應(yīng)的線程狀態(tài)對象就保存在這個變量里,當(dāng)Python調(diào)度線程時,會將被激活的線程所對應(yīng)的線程狀態(tài)對象賦給_PyThreadState_Current,使其始終保存著活動線程的狀態(tài)對象。
這就引出了這樣的一個問題:Python如何在調(diào)度進(jìn)程時,獲得被激活線程對應(yīng)的狀態(tài)對象?Python內(nèi)部會通過一個單向鏈表來管理所有的Python線程的狀態(tài)對象,當(dāng)需要尋找一個線程對應(yīng)的狀態(tài)對象時。#t#
就遍歷這個鏈表,搜索其對應(yīng)的狀態(tài)對象。在此后的描述中,我們將這個鏈表稱為“狀態(tài)對象鏈表”。下面我們來看一看實(shí)現(xiàn)這個機(jī)制的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)在Python中,對于這個狀態(tài)對象鏈表的訪問,不必在GIL的保護(hù)下進(jìn)行。
因?yàn)閷τ谶@個狀態(tài)對象鏈表,Python線程會創(chuàng)建一個獨(dú)立的鎖,專職對狀態(tài)對象鏈表進(jìn)行保護(hù)。這個鎖的創(chuàng)建是在Python進(jìn)行初始化的時候完成的。PyThread_create_key將創(chuàng)建一個新的key。注意,這里的key都是一個整數(shù),而且,當(dāng)PyThread_create_key***次被調(diào)用時(在_PyGILState_Init中的調(diào)用正是***次調(diào)用)。
會通過PyThread_allcate_lock創(chuàng)建一個keymutex。根據(jù)我們前面的分析,這個keymutex實(shí)際上和GIL一樣,都是一個PNRMUTEX結(jié)構(gòu)體,而在這個結(jié)構(gòu)體中,維護(hù)著一個Win32下的Event內(nèi)核對象。這個keymutex的功能就是用來互斥對狀態(tài)對象鏈表的訪問。
在_PyGILState_Init中,創(chuàng)建的新key被Python維護(hù)的全局變量autoTLSkey接收,其中的TLS是Thread Local Store的縮寫,這個autoTLSkey將用作Python保存所有線程的狀態(tài)對象的一個參數(shù)。的key值。也就是說,狀態(tài)對象列表中所有key結(jié)構(gòu)體中的key值都會是autoTLSkey。哎,那位看官說了,你看PyThread_create_key返回的是nkeys的遞增后的值啊。
就是說每create一次,得到的結(jié)果都是不同的,怎么能說所有的key都是一樣的呢?事實(shí)上,在整個Python的源碼中,PyThread_create_key只在_PyGILState_Init中被調(diào)用了,而這個_PyGILState_Init只會在Python運(yùn)行時環(huán)境初始化時調(diào)用一次。















 
 
 




 
 
 
 