當前位置:
首頁 > 知識 > 什麼是python的全局解釋鎖(GIL)?

什麼是python的全局解釋鎖(GIL)?

什麼是python的全局解釋鎖(GIL)?

Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。

目錄GIL解決了Python中的什麼問題?為什麼選取GIL作為解決方案?對多線程Python程序的影響為什麼GIL還沒有被刪除?為什麼在Python 3 中GIL沒有被移除?如何處理Python中的GIL?

我們所說的Python全局解釋鎖(GIL)簡單來說就是一個互斥體(或者說鎖),這樣的機制只允許一個線程來控制Python解釋器。

這就意味著在任何一個時間點只有一個線程處於執行狀態。GIL對執行單線程任務的程序員們來說並沒什麼顯著影響,但是它成為了計算密集型(CPU-bound)和多線程任務的性能瓶頸。

由於GIL即使在擁有多個CPU核的多線程框架下都只允許一次運行一個線程,所以在Python眾多功能中其聲譽可謂是「臭名昭著」。

在這篇文章中,你將了解到GIL是如何影響到你的Python程序性能的以及如何減輕它對代碼帶來的影響。

GIL解決了Python中的什麼問題?

Python利用引用計數來進行內存管理,這就意味著在Python中創建的對象都有一個引用計數變數來追蹤指向該對象的引用數量。當數量為0時,該對象佔用的內存即被釋放。

我們來通過一個簡單的代碼演示引用計數是如何工作的:

什麼是python的全局解釋鎖(GIL)?

在上述例子中,空列表對象的引用計數為3。該列表對象被a、b和傳遞給sys.getrefcount的參數引用。

回到GIL本身:

問題在於,這個引用計數變數需要在兩個線程同時增加或減少時從競爭條件中得到保護。如果發生了這種情況,可能會導致泄露的內存永遠不會被釋放,抑或更嚴重的是當一個對象的引用仍然存在的情況下錯誤地釋放內存。這可能會導致Python程序崩潰或帶來各種詭異的bug。

通過對跨線程分享的數據結構添加鎖定以至於數據不會不一致地被修改,這樣做可以很好的保證引用計數變數的安全。

但是對每一個對象或者對象組添加鎖意味著會存在多個鎖這也就導致了另外一個問題——死鎖(只有當存在多個鎖時才會發生)。而另一個副作用是由於重複獲取和釋放鎖而導致的性能下降。

GIL是解釋器本身的一個單一鎖,它增加的一條規則表明任何Python位元組碼的執行都需要獲取解釋鎖。這有效地防止了死鎖(因為只存在一個鎖)並且不會帶來太多的性能開銷。但是這的確使每一個計算密集型任務變成了單線程。

GIL雖然也被其他語言解釋器使用(如Ruby),但是這不是解決這個問題的唯一辦法。一些編程語言通過使用除引用計數以外的方法(如垃圾收集)來避免GIL對線程安全內存管理的請求。

從另一方面來看,這也意味著這些語言通常需要添加其他性能提升功能(如JIT編譯器)來彌補GIL單線程性能優勢的損失。

為什麼選取GIL作為解決方案?

那麼為什麼在Python中使用了這樣一種看似絆腳石的技術呢?這是Python開發人員的一個錯誤決定么?

正如Larry Hasting所說,GIL的設計決定是Python如今受到火熱追捧的重要原因之一。

當操作系統還沒有線程的概念的時候Python就一直存在著。Python設計的初衷是易於使用以便更快捷地開發,這也使得越來越多的程序員開始使用Python。

人們針對於C庫中那些被Python所需的功能寫了許多擴展,為了防止不一致變化,這些C擴展需要線程安全內存管理,而這些正是GIL所提供的。

GIL是非常容易實現而且很容易添加到Python中。因為只需要管理一個鎖所以對於單線程任務來說帶來了性能提升。

非線程安全的C庫變得更容易集成,而這些C擴展則成為Python被不同社區所接受的原因之一。

正如您所看到的,GIL是CPython開發者在早期Python生涯中面對困難問題的一種實用解決方案。

對多線程Python程序的影響

當你留意一些典型的Python程序或任何計算機程序時你會發現一個程序針對計算密集型和I/O密集型任務之間的性能表現是有所差異的。

計算密集型任務是那些促使CPU達到極限的任務。這其中包括了進行數學計算的程序,如矩陣相乘、搜索、圖像處理等。

I/O密集型任務是一些需要花費時間來等待來自用戶、文件、資料庫、網路等的輸入輸出的任務。I/O密集型任務有時需要等待非常久直到他們從數據源獲取到他們所需要的內容為止。這是因為在準備好輸入輸出之前數據源本身需要先進行自身處理。舉例來說,一個用戶考慮在輸入提示中輸入什麼或者在其自己進程中運行的資料庫查詢。

讓我們先來看一個執行倒計時的簡單的計算密集型程序:

什麼是python的全局解釋鎖(GIL)?

在我的4核系統上運行得到以下輸出:

什麼是python的全局解釋鎖(GIL)?

接下來我對代碼做出微調,使用兩個線程並行處理來完成倒計時:

什麼是python的全局解釋鎖(GIL)?

接下來我再次運行:

什麼是python的全局解釋鎖(GIL)?

正如你所看到的,兩個版本的完成時間相差無幾。在多線程版本中GIL阻止了計算密集型任務線程並行執行。

GIL對I/O密集型任務多線程程序的性能沒有太大的影響,因為在等待I/O時鎖可以在多線程之間共享。

但是對於一個線程是完全計算密集型的任務來說(例如,利用線程進行部分圖像處理)不僅會由於鎖而變成單線程任務而且還會明顯的增加執行時間。正如上例中多線程與完全單線程相比的結果。

這種執行時間的增加是由於鎖帶來的獲取和釋放開銷。

為什麼GIL還沒有被刪除?

Python的開發者收到了許許多多關於這方面的抱怨,但是像Python這樣極受歡迎的語言無法做出去除GIL這樣的巨變同時還不造成向後不兼容問題。

GIL顯然是可以被刪除的,而且在過去這項任務也被開發者和研究人員多次完成。但是所有的嘗試打破了在很大程度上取決於由GIL提供解決方案的C擴展市場。

當然,還有許多其他解決方案可以解決GIL問題,但是其中一些以犧牲單線程和多線程I/O密集型任務的性能表現為代價,而另外一些解決方法又過於複雜。畢竟新版本發布後你不會希望你的Python跑得慢了些。

BDFL of Python的創始人Guido van Rossum在2007年09月的文章《It isn』t Easy to remove the GIL》中向社區做出回答:

「如果單線程任務和多線程I/O密集型任務的性能表現不會下降,那麼我十分希望Py3k中能出現一組修補程序。」

當然了,此後的每一次嘗試都沒有滿足這個條件。

為什麼在Python 3 中GIL沒有被移除?

Python3中的確有機會使得許多功能從零開始,並且在這個過程中打破了那些需要更改和更新的C擴展並且將其移植到Python 3中。這也是為什麼Python 3的早期版本被社區採納的較慢的原因。

但是為什麼GIL沒有被刪除?

刪除GIL會使得Python 3在處理單線程任務方面比Python 2慢,可以想像會產生什麼結果。你不能否認GIL帶來的單線程性能優勢,這也就是為什麼Python 3中仍然還有GIL。

但是Python 3的確對現有GIL做了重大改進。

我們僅僅討論了GIL對「僅計算密集型任務」和「僅I/O密集型任務」的影響,但是對於那些一部分線程是計算密集型一部分線程是I/O密集型的程序來說會怎麼樣呢?

在這樣的程序中,Python的GIL通過不讓I/O密集型線程從計算密集型線程獲取GIL而使I/O密集型線程陷入癱瘓。

這是因為Python中內嵌了一種機制,這個機制在固定連續使用時間後強迫線程釋放GIL,並且如果沒人獲取這個GIL,那麼同一線程可以繼續使用。

什麼是python的全局解釋鎖(GIL)?

這個機制面臨的問題是大多數計算密集型線程會在別的線程獲取GIL之前再次獲取GIL。這個研究工作由David Beazley進行,並且你可以在這裡得到可視化資源。

Antoine Pitrou於2009年在Python3.2中解決了這個問題,他添加了一種機制來查看其他線程請求GIL的訪問數量,當數量下降時不允許當前線程在其他線程有機會運行之前重新獲取GIL。

如何處理Python中的GIL?

如果GIL給你帶來困擾,你可嘗試一下方法:

多進程vs多線程:最流行的方法是應用多進程方法,在這個方法中你使用多個進程而不是多個線程。每一個Python進程都有自己的Python解釋器和內存空間,因此GIL不會成為問題。Python擁有一個multiprocessing模塊可以幫助我們輕鬆創建多進程:

什麼是python的全局解釋鎖(GIL)?

在系統上運行得到

什麼是python的全局解釋鎖(GIL)?

相比於多線程版本,性能有所提升。

但是時間並沒有下降到我們之前版本的一半,這是因為進程管理有自己的開銷。多進程比多線程更「重」,因此請記住,這可能成為規模瓶頸。

替代Python解釋器:Python中有多個解釋器實現辦法,分別用C,Java,C#和Python編寫的CPython,JPython,IronPython和PyPy是最受歡迎的。GIL只存在於傳統的Python實現方法如CPython中。如果你的程序及其庫文件可以通過別的實現方式實現,那麼你也可以嘗試一下。

等等看吧:許多用戶利用GIL提升了單線程任務性能表現。當然多線程程序員們也不必為此煩惱,因為Python社區內的一些聰明大腦們正在致力於從CPython中刪除GIL。其中一種嘗試為Giletomy。

Python GIL經常被認為是一個神秘而困難的話題。但是請記住作為一名Python支持者,只有當您正在編寫C擴展或者您的程序中有計算密集型的多線程任務時才會被GIL影響。

在這種情況下,這篇文章應該給了你需要的一切去了解GIL是什麼以及如何在自己的項目中處理它。如果您希望了解GIL的低層次內部運行,我建議您觀看David Beazley的Understanding the Python GIL。

英文原文:https://realpython.com/blog/python/python-gil/
譯者:Quinn_W

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python部落 的精彩文章:

在Python 3.5中通過零用戶空間內存的套接字進行文件傳輸
我用4年時間解決了Python GIL的一個bug……

TAG:Python部落 |