python多線程機制


Python中的線程從一開始就是操作系統的原生線程。而Python虛擬機也同樣使用一個全局解釋器鎖(Global Interpreter Lock,GIL)來互斥線程多Python虛擬機的使用。

  1. GIL與線程調度

  為了理解Pyhon為什么需要GIL,考慮這樣的情形:假設有兩個線程A B,在兩個線程中,都同時保存着對內存中同一對象obj的引用,也就是說,這事obj->ob_refcnt的值為2.如果A銷毀對obj的引用,顯然,A將通過Py_DECREF調整obj的引用計數值。外面知道,py_DECREF的整個動作可以分為兩個部分:

  --obj ->ob_refcnt;

  if(obj->ob_refcnt == 0) destory object and free memory.

  如果A執行完第一個動作后,obj->ob_refcnt的值變為1,不幸的是,在這里時候線程調度機制將A掛起,喚醒了B。更為不幸的是,B同樣也開始銷毀對obj的引用。B完成第一個動作后,obj ->ob_refcnt為0,B是一個幸運兒,它沒有被線程調度打斷,而是順利完成了接下來的第二個動作,將對象銷毀,內存釋放。好了嗎,現在A又被重新喚醒,可現在已是物是人非,obj ->ob_refcnt已經被B減少到0,而不是當時的1.按照約定,A開始在一次地對已經銷毀的對象進行對象銷毀的內存釋放動作。結局是什么?只有天知道………………

  為了支持多線程機制,一個基本的要求就是需要實現不同線程對共享資源訪問的互斥。Python也不例外,這正是引入GIL的根源所在。Python中的GIL是一個非常霸道的互斥實現,正如它的名字所暗示的,GIL是一個解釋器(Interpreter)。也就是說,在一個線程擁有了解釋器的訪問權之后,其他的所有線程都必須等待它釋放解釋器的訪問權,即使這些線程的下一條指令並不會互相影響。初看上去,這樣的保護機制粒度太大了,我們似乎只需要將可能被多個線程共享的資源保護起來即可,對於不會被多個線程共享的資源,完全可以不用保護。實際上,在Python發展的歷史中,的確出現過這樣的解決方案,但令人驚奇的,這樣的方案在單處理器上的多線程實現效率上卻沒有GIL的方案好,所以現在python中的多線程機制是在GIL的基礎上實現的。

  當然,這樣的方案也就意味着,無論如何,在同一時間,只能有一個線程能訪問python所提供的API。注意這里的同一時間對於單處理器是毫無意義的,因為單處理器的本質是不可能並行的,但是多處理器就完全不同了,同一時間,的確可以有多個線程獨立運行,然而python的GIL限制了這樣的情形,是的多處理器最終退化為單處理器,性能大打折扣。

    

 

  2.

  對於python而言,字節碼解釋器是python的核心所在,所以Python通過GIL來互斥不用線程對解釋器的使用。

  如圖所示,A B C都需要使用解釋器來執行字節碼,以完成某種計算,但是在這之前,他們必須獲得GIL,因為GIL把守這通往字節碼解釋器的大門。當A獲得GIL之后,其他兩個線程B C只能等待A釋放GIL后,才能進入解釋器,執行一些計算。

  實際上,Python的GIL背后所保護的不僅僅是Python的解釋器,同樣還有Python的C API,在C/C++和Python的混合開發中,在涉及到原生線程和Python線程的相互協作時,也需要GIL進行互斥。

  那么A在何時釋放GIL呢?如果等到A使用完解釋器之后,才釋放GIL,這也就意味着,並行計算退化了為了串行的計算,毫無疑問,Python擁有一探線程的調度機制。

  對於線程的調度機制而言,同操作系統的進程調度一樣,最關鍵要解決兩個問題:

  • 在何時掛起當前的線程,選擇處於等待狀態的下一個線程?
  • 在眾多的處於等待狀態的線程中,選擇激活哪一個線程?

  在python多線程的機制中,這兩個問題是分別由不同的層次解決的。對於何時進行線程調度的問題,是由python自身決定的。考慮一下操作系統是如何進行進程的切換的。當一個進程執行了一段時間后,發生了時鍾中斷,操作系統響應時鍾中斷,並在這時開始進行進程的調度。同樣,python中也是通過軟件模擬了這樣的時鍾中斷,來激活線程的調度。我們知道,python的字節碼解釋器的工作原理是按照指令的順序一條一條的順序執行,Python內部維護着一個數值,這個數值就是Python內部的時鍾,如果這個數值為N,則意味着Python在執行了N條指令以后應該立即啟動線程調度機制。

1 import  sys
2 print sys.getcheckinterval()
View Code

上面代碼的執行結果,Python默認是在執行了100條指令后啟動線程調度機制。實際上,這個值不僅僅用來進行線程調度,在內部,Python也使用它來檢查是否有異步的時間(envent)發生,需要處理。我們可以通過 sys.setcheckinterval() 來調節這個值。

  那么究竟python會在眾多等待線程中選擇哪一個幸運兒呢?答案是,不知道。對於這個問題,Python完全沒有插手,而是交給了底層的操作系統來解決。也就是說python借用了底層操作系統所提供的線程調度機制來決定下一個進入Python解釋器的線程究竟是誰。

  這一點至關重要,這就意味着Python中的線程實際上就是操作系統所支持的原生線程,並不是模擬出來的。Python中的多線程機制也是建立在操作系統的原生線程的基礎之上,對應不同的操作系統,有不同的實現,然而最終,在不同的原生線程基礎上,Python提供了一套統一的抽象機制,給Python的使用者一個非常簡單而方便的多線程工具箱,這就是python中的兩個Module:thread以及在其之上的threading。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM