Python 最難的問題

Python 最難的問題

超過十年以上,沒有比解釋器全局鎖(GIL)讓Python新手和專家更有挫折感或者更有好奇心。python

未解決的問題

隨處都是問題。難度大、耗時多確定是其中一個問題。僅僅是嘗試解決這個問題就會讓人驚訝。以前是整個社區的嘗試,但如今只是外圍的開發人員在努力。對於新手,去嘗試解決這樣的問題,主要是由於問題難度足夠大,解決以後能夠得到至關的榮譽。計算機科學中未解決的 P = NP 就是這樣的問題。對此若是能給出多項式時間複雜度的答案,那簡直就能夠改變世界了。Python最困難的問題比證實P = NP要容易一些,不過迄今仍然沒有一個滿意的解決,要知道,這個問題的實用的解決方案一樣能起着變革性的做用。正由於如此,很容易看到Python社區會有如此多的人關注於這樣的問題: "對於解釋器全局鎖能作什麼?"程序員

Python的底層

要理解GIL的含義,咱們須要從Python的基礎講起。像C++這樣的語言是編譯型語言,所謂編譯型語言,是指程序輸入到編譯器,編譯器再根據語言的語法進行解析,而後翻譯成語言獨立的中間表示,最終連接成具備高度優化的機器碼的可執行程序。編譯器之因此能夠深層次的對代碼進行優化,是由於它能夠看到整個程序(或者一大塊獨立的部分)。這使得它能夠對不一樣的語言指令之間的交互進行推理,從而給出更有效的優化手段。編程

與此相反,Python是解釋型語言。程序被輸入到解釋器來運行。解釋器在程序執行以前對其並不瞭解;它所知道的只是Python的規則,以及在執行過程當中怎樣去動態的應用這些規則。它也有一些優化,可是這基本上只是另外一個級別的優化。因爲解釋器無法很好的對程序進行推導,Python的大部分優化實際上是解釋器自身的優化。更快的解釋器天然意味着程序的運行也能「免費」的更快。也就是說,解釋器優化後,Python程序不用作修改就能夠享受優化後的好處。安全

這一點很重要,讓咱們再強調一下。若是其餘條件不變,Python程序的執行速度直接與解釋器的「速度」相關。無論你怎樣優化本身的程序,你的程序的執行速度仍是依賴於解釋器執行你的程序的效率。這就很明顯的解釋了爲何咱們須要對優化Python解釋器作這麼多的工做了。對於Python程序員來講,這恐怕是與免費午飯最接近的了。多線程

免費午飯結束了

仍是沒有結束?摩爾定律給出了硬件速度會按照肯定的時間週期增加,與此同時,整整一代程序員學會了如何編碼。若是一我的寫了比較慢的代碼,最簡單的結果一般是更快的處理器去等待代碼的執行。顯然,摩爾定律仍然是正確的,而且還會在很長一段時間生效,不過它說起的方式有了根本的變化。並不是是時鐘頻率增加到一個遙不可及的速度,而是經過多核來利用晶體管密度提升帶來的好處。在新處理器上運行的程序要想充分利用其性能,必須按照併發方式進行重寫。併發

大部分開發者聽到「併發」一般會馬上想到多線程的程序。目前來講,多線程執行仍是利用多核系統最經常使用的方式。儘管多線程編程大大好於「順序」編程,不過即使是仔細的程序員也無法在代碼中將併發性作到最好。編程語言在這方面應該作的更好,大部分應用普遍的現代編程語言都會支持多線程編程。app

意外的事實

如今咱們來看一下問題的癥結所在。要想利用多核系統,Python必須支持多線程運行。做爲解釋型語言,Python的解釋器必須作到既安全又高效。咱們都知道多線程編程會遇到的問題。解釋器要留意的是避免在不一樣的線程操做內部共享的數據。同時它還要保證在管理用戶線程時保證老是有最大化的計算資源。編程語言

那麼,不一樣線程同時訪問時,數據的保護機制是怎樣的呢?答案是解釋器全局鎖。從名字上看能告訴咱們不少東西,很顯然,這是一個加在解釋器上的全局(從解釋器的角度看)鎖(從互斥或者相似角度看)。這種方式固然很安全,可是它有一層隱含的意思(Python初學者須要瞭解這個):對於任何Python程序,無論有多少的處理器,任什麼時候候都老是隻有一個線程在執行。性能

許多人都是偶然發現這個事實的。網上的不少討論組和留言板都充斥着來自Python初學者和專家的相似這樣的問題——」爲何我全新的多線程Python程序運行得比其只有一個線程的時候還要慢?「許多人在問這個問題時仍是很是犯暈的,由於顯然一個具備兩個線程的程序要比其只有一個線程時要快(假設該程序確實是可並行的)。事實上,這個問題被問得如此頻繁以致於Python的專家們精心製做了一個標準答案:」不要使用多線程,請使用多進程。「但這個答案比那個問題更加讓人困惑。難道我不能在Python中使用多線程?在Python這樣流行的一個語言中使用多線程到底是有多糟糕,連專家都建議不要使用。難道我真的漏掉了一些東西?優化

很遺憾,沒有任何東西被漏掉。因爲Python解釋器的設計,使用多線程以提升性能應該算是一個困難的任務。在最壞的狀況下,它將會下降(有時很明顯)你的程序的運行速度。一個計算機科學與技術專業的大學生新手可能會告訴你當多個線程都在競爭一個共享資源時將會發生什麼。結果一般不會很是理想。不少狀況下多線程都能很好地工做,可能對於解釋器的實現和內核開發人員來講,沒有關於Python多線程性能的過多抱怨。

如今該怎麼辦?驚慌?

那麼,這又能怎樣?問題解決了嗎?難道咱們做爲Python開發人員就意味着要放棄使用多線程來探索並行的想法了?爲何不管怎樣,GIL須要保證只有一個線程在某一時刻處於運行中?難道不能夠添加細粒度的鎖來阻止多個獨立對象的同時訪問?而且爲何以前沒有人去嘗試過相似的事情?

這些實用的問題有着十分有趣的回答。GIL對諸如當前線程狀態和爲垃圾回收而用的堆分配對象這樣的東西的訪問提供着保護。然而,這對Python語言來講沒什麼特殊的,它須要使用一個GIL。這是該實現的一種典型產物。如今也有其它的Python解釋器(和編譯器)並不使用GIL。雖然,對於CPython來講,自其出現以來已經有不少不使用GIL的解釋器。

那麼爲何不拋棄GIL呢?許多人也許不知道,在1999年,針對Python 1.5,一個常常被提到但卻不怎麼理解的「free threading」補丁已經嘗試實現了這個想法,該補丁來自Greg Stein。在這個補丁中,GIL被徹底的移除,且用細粒度的鎖來代替。然而,GIL的移除給單線程程序的執行速度帶來了必定的代價。當用單線程執行時,速度大約下降了40%。使用兩個線程展現出了在速度上的提升,但除了這個提升,這個收益並無隨着核數的增長而線性增加。因爲執行速度的下降,這一補丁被拒絕了,而且幾乎被人遺忘。

移除GIL很是困難,讓咱們去購物吧!

(譯者注:XXX is hard. Let's go shopping!在英語中相似於中文的咆哮體。其隱含意思爲想成功完成某件事情很是困難,咱們去直接尋找第三方的產品替代吧。)

不過,「free threading」這個補丁是有啓發性意義的,其證實了一個關於Python解釋器的基本要點:移除GIL是很是困難的。因爲該補丁發佈時所處的年代,解釋器變得依賴更多的全局狀態,這使得想要移除當今的GIL變得更加困難。值得一提的是,也正是由於這個緣由,許多人對於嘗試移除GIL變得更加有興趣。困難的問題每每頗有趣。

可是這可能有點被誤導了。讓咱們考慮一下:若是咱們有了一個神奇的補丁,其移除了GIL,而且沒有對單線程的Python代碼產生性能上的降低,那麼什麼事情將會發生?咱們將會得到咱們一直想要的:一個線程API可能會同時利用全部的處理器。那麼如今,咱們已經得到了咱們但願的,但這確實是一個好事嗎?

基於線程的編程毫無疑問是困難的。每當某我的以爲他了解關於線程是如何工做的一切的時候,老是會悄無聲息的出現一些新的問題。由於在這方面想要獲得正確合理的一致性真的是太難了,所以有一些很是知名的語言設計者和研究者已經總結得出了一些線程模型。就像某個寫過多線程應用的人能夠告訴你的同樣,無論是多線程應用的開發仍是調試都會比單線程的應用難上數倍。程序員一般所具備的順序執行的思惟模偏偏就是與並行執行模式不相匹配。GIL的出現無心中幫助了開發者免於陷入困境。在使用多線程時仍然須要同步原語的狀況下,GIL事實上幫助咱們保持不一樣線程之間的數據一致性問題。

那麼如今看起來討論Python最可貴問題是有點問錯了問題。咱們有很是好的理由來講明爲何Python專家推薦咱們使用多進程代替多線程,而不是去試圖隱藏Python線程實現的不足。更進一步,咱們鼓勵開發者使用更安全更直接的方式實現併發模型,同時保留使用多線程進行開發除非你覺的真的很是必要的話。對於大多數人來講什麼是最好的並行編程模型可能並非十分清楚。可是目前咱們清楚的是多線程的方式可能並非最好的。

至於GIL,不要認爲它在那的存在就是靜態的和未經分析過的。Antoine Pitrou 在Python 3.2中實現了一個新的GIL,而且帶着一些積極的結果。這是自1992年以來,GIL的一次最主要改變。這個改變很是巨大,很難在這裏解釋清楚,可是從一個更高層次的角度來講,舊的GIL經過對Python指令進行計數來肯定什麼時候放棄GIL。這樣作的結果就是,單條Python指令將會包含大量的工做,即它們並無被1:1的翻譯成機器指令。在新的GIL實現中,用一個固定的超時時間來指示當前的線程以放棄這個鎖。在當前線程保持這個鎖,且當第二個線程請求這個鎖的時候,當前線程就會在5ms後被強制釋放掉這個鎖(這就是說,當前線程每5ms就要檢查其是否須要釋放這個鎖)。當任務是可行的時候,這會使得線程間的切換更加可預測。

然而,這並非一個完美的改變。對於在各類類型的任務上有效利用GIL這個領域裏,最活躍的研究者可能就是David Beazley了。除了對Python 3.2以前的GIL研究最深刻,他還研究了這個最新的GIL實現,而且發現了不少有趣的程序方案。對於這些程序,即便是新的GIL實現,其表現也至關糟糕。他目前仍然經過一些實際的研究和發佈一些實驗結果來引領並推動着有關GIL的討論。

無論某一我的對Python的GIL感受如何,它仍然是Python語言裏最困難的技術挑戰。想要理解它的實現須要對操做系統設計、多線程編程、C語言、解釋器設計和CPython解釋器的實現有着很是完全的理解。單是這些所需準備的就妨礙了不少開發者去更完全的研究GIL。雖然如此,並無跡象代表GIL在不久之後的任何一段時間內會遠離咱們。目前,它將繼續給那些新接觸Python,而且與此同時又對解決很是困難的技術問題感興趣的人帶來困惑和驚喜。

以上內容是基於我目前對Python解釋器所作出的研究而寫。雖然我還但願寫一些有關解釋器的其它方面內容,可是沒有任何一個比全局解釋器鎖(GIL)更爲人所知。雖然我認爲這裏有些內容是不許確的,可是這些技術上的細節與CPython的不少資源條目是不一樣的。若是你發現了不許確的內容,請及時告知我,這樣我就會盡快對其進行改正。

原文連接:
https://www.oschina.net/translate/pythons-hardest-problem


識別圖中二維碼,領取python全套視頻資料

相關文章
相關標籤/搜索