前言
我們在使用python開發的過程中時常聽到GIL這個詞,并且發現這個詞經常和Python無法高效的實現多線程關聯在一起,關于python多線程的實現在前面的文章已經介紹過,本文我們主要來了解一下GIL到底是什么?為什么會影響python的多線程。
?
一、什么是GIL
GIL全稱 Global Interpreter Lock ,官方給出的解釋如下:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents
multiple native threads from executing Python bytecodes at once. This lock
is necessary mainly because CPython’s memory management is not thread-safe.
(However, since the GIL exists, other features have grown to depend on the
guarantees that it enforces.)
我們知道,和PHP一樣,Python也是一門腳本語言,它的特點就是一邊解釋一邊執行。既然不是編譯型語言,那么就需要解析器來解析和執行。Python的執行環境,可用的解析器有很多種,例如CPython、JPython、IronPython等等,其中我們最為常用的、也同時是python安裝時默認的解析器就是CPython。所以要明確一點, GIL并不是Python本身自帶的特性,而是CPython解析器所采用的一種解析方案 。
現在的許多編程語言,為了更有效的利用多核處理器的性能,就出現了多線程的編程方式,腳本語言很多都不能支持多線程,例如PHP,但是Python為了利用多核,Python開始支持多線程。而使用多線程隨之而來的就是線程間數據一致性和狀態同步的問題。我們 通常在解決多線程之間數據完整性和狀態同步的方法就是給線程加鎖 ,像Java、C++等編譯型語言通常都需要程序員自己手動添加和釋放線程鎖。而Python語言在設計時為了追求簡易性和便利性,就設計了GIL這個全局的鎖。如官方所說, GIL是一把全局排他鎖 ,而后面很多Python庫都嚴重依賴GIL,導致不能隨意修改源碼,這便是GIL的來源。
?
二、GIL對多線程的影響
盡管Python完全支持多線程編程, 但是CPython解釋器的實現部分在完全并行執行時并不是線程安全的。 實際上,這時候解釋器被GIL保護著,它確保 任何時候都只有一個Python線程執行 。 GIL最大的問題就是Python的多線程程序并不能利用多核CPU的優勢,甚至就幾乎等于Python是個單線程的程序 (Python里面只有多進程才能利用到多核,而Java等編譯型語言多線程就能利用多核資源)。
在這里有一點要強調的是 GIL只會影響到那些嚴重依賴CPU的程序 (比如計算型的)。 如果你的程序大部分只會涉及到I/O,比如網絡交互,那么使用Python多線程就很合適, 因為它們大部分時間都在等待,而 GIL在程序進入I/O操作等待時是可以釋放的 。
所以需要注意的是,并不是所有的多線程就能提高程序的運行效率。比如當你使用一個計算密集型的線程去管理圖形計算界面,又開了很多別的線程去進行異步I/O操作,那么這個計算密集型的線程會長時間持有GIL,而那些別的進程就只能等待,期間還有線程的創建和管理的性能損耗,總的來說反而性能更差了。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖片來源于網絡
?
三、如何避免GIL的影響
為了利用多核資源,又能避免GIL對多線程性能的影響,這里介紹一下Python社區現有的解決方案:
1、使用C擴展
這種方案的主要思想是將計算密集型任務轉移給C,跟Python獨立,在工作的時候在C代碼中釋放GIL。這樣在計算密集的時候程序執行速度也會提升的很快。例如你要操作數組,那么使用NumPy這樣的擴展會非常的高效。但是同樣帶來不好的問題就是python需要依賴很多C擴展,喪失了一部分簡單易用的特性。
2、使用 multiprocessing 庫實現多進程
Python的多線程雖然是單核多線程,但是多進程卻是可以真正利用多核資源的,多個Python進程有各自獨立的GIL鎖,互不影響。
為了彌補thread庫因為GIL而低效的缺陷,出現了multiprocessing庫。它完整的復制了一套thread所提供的接口方便遷移。唯一的不同就是它使用了多進程而不是多線程。 當一個線程想要執行CPU密集型工作時,會將任務發給進程池。 然后進程池會在另外一個進程中啟動一個單獨的Python解釋器來工作,這時候原本的線程相當于執行I/O操作,當線程等待I/O結果的時候會釋放GIL。
這種方式也有缺陷,當混合使用線程和進程池的時候會增加程序實現時線程間數據通訊和同步的困難。所以最好在程序啟動時,創建任何線程之前先創建一個單例的進程池。 然后線程使用同樣的進程池來進行它們的計算密集型工作。
3、使用其他解析器
像JPython等解析器,不需要GIL的幫助。但是由于這類是采用Java或C#等語言用于解析器實現,他們也失去了利用社區眾多C語言模塊有用特性的機會,許多C擴展的庫無法使用。關于其他解析器,也可以了解下PyPy。
?
四、總結歸納
Python GIL其實是歷史遺留問題,跟我們常常做算法是要舍棄空間還是舍棄時間是一個道理,是性能與python簡便性之間權衡后的產物:
1、具有很多CPU計算密集型的程序,GIL只能讓多線程以當個線程的形式運行,效率低下;
2、如果只是I/O密集型的操作,多線程能在一定程度上提升程序效率;
3、使用并行程序時為了避免GIL的影響,可以使用多進程、C擴展等等方式解決。
參考鏈接:http://cenalulu.github.io/python/gil-in-python/
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
