對于多核處理器,在同一時間确實可以多個線程獨立運作,但在python中确不是這樣的了。原因在于,python虛拟機中引入了gil這一概念。gil(global interpreter lock)全局解析器鎖是用來解決共享資源通路的互斥問題,導緻在python虛拟機中同一時間隻能有一個線程通路python所提供的api。
在作業系統中系統通過時鐘中斷進行程序的排程,而python正是參考這個原理。在python内部維護了一個内部的時鐘,來記錄每個線程每個時鐘周期執行指令的數量。
當一個線程獲得了python虛拟機的gil後可以按順序執行100條指令,然後挂起目前程序,切換下一個等待執行的線程。
python并沒有去實作一個線程優先級排程算法,而是将線程選擇問題交給了底層的作業系統,也就是說python借用了底層作業系統所提供的線程排程機制來決定下一個執行的線程。
是以,python使用的就是作業系統原生的線程,隻是python在其基礎之上提供了一套統一的抽象機制。
在作業系統中,程序之間的切換需要不斷儲存和恢複程序之間的上下文環境,保證每一個進行都能在其對應的上下文環境中運作。python正是參考作業系統的切換機制,為每一個線程建立一個儲存線程狀态資訊的pyframeobject對象。在python中有有一個全局變量<code>pythreadstate *_pythreadstate_current</code>用來儲存目前活動線程的線程狀态對象。
在python中通過一個單項連結清單來管理所有的python線程對象(保護線程的狀态資訊和線程資訊,例如線程id),當需要尋找一個線程對應的狀态對象時,就周遊這個連結清單,搜尋其對應的狀态對象。
這個狀态對象連結清單并不會受到gil的保護,而是有其專用的鎖。
需要注意
目前活動的python線程不一定是獲得了gil的線程,例如“主線程獲得了gil,子線程還沒有申請到gil時也沒有挂起,而且主線程和子線程都是作業系統原生的線程,作業系統可能在主線程和子線程之間進行切換(作業系統的線程切換是不受python虛拟機控制的,屬于作業系統自身行為)”。python虛拟機的排程是一定是獲得gil基礎之上的,而作業系統級的就不一定獲得gil了。
雖有作業系統會把未獲得gil的線程切換為活動線程,但是該線程發現自身并沒有獲得gil會自動挂起。
隻有當所有線程都完成了初始化操作,作業系統的線程排程和python線程排程才會一緻。那時,python的線程排程會迫使目前活動線程釋放gil,導緻觸發gil中維護的event核心對象,進而觸發作業系統的線程排程。(在初始化完成之前,python線程排程和作業系統排程之間沒有因果關系)
在python中如果有raw_input等待輸入的操作時将自身阻塞後,并将等待gil線程喚醒,這種情況成為阻塞排程。
線上程通過阻塞排程切換時,python内部的時鐘周期技術<code>_py_ticker</code>依然會被保持,不會被重置。
python的主線程銷毀和子線程銷毀是不同的,子線程隻需要維護引用計數,而主線程還需要銷毀運作環境。
上面讨論的gil屬于python内合計互斥,實作了保護記憶體的共享資源。而使用者級互斥保護了使用者程式中的共享資源。
python中提供了lock機制來實作線程之間的互斥。當線程通過lock.acquire獲得lock之後,子線程會因為等待lock而挂起,直到主線程釋放lock之後才會被python的線程排程機制喚醒。
線上程執行過程中如果出現需要等待另一個lock資源的時候,需要将gil轉交給其他等待gil的線程以避免死鎖。