對Python線程內(nèi)容進行全講析
怎么區(qū)分哪個Python線程對應哪個狀態(tài)對象呢?首先考慮的是我們還有線程的ID。ID存儲的正是各個線程的ID,根據(jù)這些ID,就可以輕輕松松的進行Python線程內(nèi)容的尋找了。
每一個線程對應的線程狀態(tài)對象都保存著這個線程當前的PyFrameObject對象,線程的id這樣一些信息。有時候,線程是需要訪問這些信息的。比如考慮一個最簡單的情形,在某種情況下。
每個線程都需要訪問線程狀態(tài)對象中所保存的thread_id信息,顯然,線程A獲得的應該是A的thread_id,線程B亦然。倘若線程A獲得的是B的thread_id,那就壞菜了。這就意味著Python內(nèi)部必須有一套機制,這套機制與操作系統(tǒng)管理進程的機制非常類似。
我們知道,在操作系統(tǒng)從進程A切換到進程B時,首先會保存進程A的上下文環(huán)境,再進行切換;當從進程B切換回進程A時,又會恢復進程A的上下文環(huán)境,這樣就保證了進程A始終是在屬于自己的上下文環(huán)境中運行。
這里的線程狀態(tài)對象就等同于進程的上下文,Python線程內(nèi)容同樣會有一套存儲、恢復線程狀態(tài)對象的機制。同時,在Python內(nèi)部,維護著一個全局變量:PyThreadState * _PyThread- State_Current。
當前活動線程所對應的線程狀態(tài)對象就保存在這個變量里,當Python調(diào)度線程時,會將被激活的線程所對應的線程狀態(tài)對象賦給_PyThreadState_Current,使其始終保存著活動線程的狀態(tài)對象。
這就引出了這樣的一個問題:Python如何在調(diào)度進程時,獲得被激活線程對應的狀態(tài)對象?Python內(nèi)部會通過一個單向鏈表來管理所有的Python線程的狀態(tài)對象。當需要尋找一個線程對應的狀態(tài)對象時,就遍歷這個鏈表,搜索其對應的狀態(tài)對象。在此后的描述中,我們將這個鏈表稱為“狀態(tài)對象鏈表”。
下面我們來看一看實現(xiàn)這個機制的關鍵數(shù)據(jù)結構。PyThread_create_key將創(chuàng)建一個新的key。注意,這里的key都是一個整數(shù)。而且,當PyThread_create_key***次被調(diào)用時(在_PyGILState_Init中的調(diào)用正是***次調(diào)用),會通過PyThread_allcate_lock創(chuàng)建一個keymutex。
根據(jù)我們前面的分析,這個keymutex實際上和GIL一樣,都是一個PNRMUTEX結構體,而在這個結構體中,維護著一個Win32下的Event內(nèi)核對象。這個keymutex的功能就是用來互斥對狀態(tài)對象鏈表的訪問。
在_PyGILState_Init中,創(chuàng)建的新key被Python維護的全局變量autoTLSkey接收,其中的TLS是Thread Local Store的縮寫。這個autoTLSkey將用作Python保存所有線程的狀態(tài)對象的一個參數(shù),即是圖15-6中的key值。也就是說,狀態(tài)對象列表中所有key結構體中的key值都會是autoTLSkey。
哎,那位看官說了,你看PyThread_create_key返回的是nkeys的遞增后的值啊,就是說每create一次,得到的結果都是不同的,怎么能說所有的key都是一樣的呢?事實上,在整個Python的源碼中,PyThread_create_key只在_PyGILState_Init中被調(diào)用了,而這個_PyGILState_Init只會在Python運行時環(huán)境初始化時調(diào)用一次。
雖然這個核心函數(shù)的名字叫find_key,然而我們可以看到,它的作用并不僅僅是搜索,而且還包含了創(chuàng)建的動作。在代碼清單15-3的[2]處,find_key會遍歷狀態(tài)對象列表,搜索key和id都匹配的key結構體。
如果搜索成功,則直接返回;而當搜索失敗時,find_key會在代碼清單15-3的[3]處創(chuàng)建一個新的key結構體,并設置其中的id,key和value,***將其插入到狀態(tài)對象列表的頭部。
在代碼清單15-3的[1]和[4]處我們看到了Python確實通過在_PyGILState_Init中創(chuàng)建的keymutex來互斥對狀態(tài)對象列表的訪問。在了解了這個核心函數(shù)之后,Python線程內(nèi)容為狀態(tài)對象列表所提供的接口就顯得非常清晰了。其實,就是簡單的鏈表的插入、刪除和查詢操作。
【編輯推薦】