前言
我們在使用python開發(fā)的過程中時(shí)常聽到GIL這個(gè)詞,并且發(fā)現(xiàn)這個(gè)詞經(jīng)常和Python無法高效的實(shí)現(xiàn)多線程關(guān)聯(lián)在一起,關(guān)于python多線程的實(shí)現(xiàn)在前面的文章已經(jīng)介紹過,本文我們主要來了解一下GIL到底是什么?為什么會(huì)影響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也是一門腳本語言,它的特點(diǎn)就是一邊解釋一邊執(zhí)行。既然不是編譯型語言,那么就需要解析器來解析和執(zhí)行。Python的執(zhí)行環(huán)境,可用的解析器有很多種,例如CPython、JPython、IronPython等等,其中我們最為常用的、也同時(shí)是python安裝時(shí)默認(rèn)的解析器就是CPython。所以要明確一點(diǎn), GIL并不是Python本身自帶的特性,而是CPython解析器所采用的一種解析方案 。
現(xiàn)在的許多編程語言,為了更有效的利用多核處理器的性能,就出現(xiàn)了多線程的編程方式,腳本語言很多都不能支持多線程,例如PHP,但是Python為了利用多核,Python開始支持多線程。而使用多線程隨之而來的就是線程間數(shù)據(jù)一致性和狀態(tài)同步的問題。我們 通常在解決多線程之間數(shù)據(jù)完整性和狀態(tài)同步的方法就是給線程加鎖 ,像Java、C++等編譯型語言通常都需要程序員自己手動(dòng)添加和釋放線程鎖。而Python語言在設(shè)計(jì)時(shí)為了追求簡易性和便利性,就設(shè)計(jì)了GIL這個(gè)全局的鎖。如官方所說, GIL是一把全局排他鎖 ,而后面很多Python庫都嚴(yán)重依賴GIL,導(dǎo)致不能隨意修改源碼,這便是GIL的來源。
?
二、GIL對多線程的影響
盡管Python完全支持多線程編程, 但是CPython解釋器的實(shí)現(xiàn)部分在完全并行執(zhí)行時(shí)并不是線程安全的。 實(shí)際上,這時(shí)候解釋器被GIL保護(hù)著,它確保 任何時(shí)候都只有一個(gè)Python線程執(zhí)行 。 GIL最大的問題就是Python的多線程程序并不能利用多核CPU的優(yōu)勢,甚至就幾乎等于Python是個(gè)單線程的程序 (Python里面只有多進(jìn)程才能利用到多核,而Java等編譯型語言多線程就能利用多核資源)。
在這里有一點(diǎn)要強(qiáng)調(diào)的是 GIL只會(huì)影響到那些嚴(yán)重依賴CPU的程序 (比如計(jì)算型的)。 如果你的程序大部分只會(huì)涉及到I/O,比如網(wǎng)絡(luò)交互,那么使用Python多線程就很合適, 因?yàn)樗鼈兇蟛糠謺r(shí)間都在等待,而 GIL在程序進(jìn)入I/O操作等待時(shí)是可以釋放的 。
所以需要注意的是,并不是所有的多線程就能提高程序的運(yùn)行效率。比如當(dāng)你使用一個(gè)計(jì)算密集型的線程去管理圖形計(jì)算界面,又開了很多別的線程去進(jìn)行異步I/O操作,那么這個(gè)計(jì)算密集型的線程會(huì)長時(shí)間持有GIL,而那些別的進(jìn)程就只能等待,期間還有線程的創(chuàng)建和管理的性能損耗,總的來說反而性能更差了。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖片來源于網(wǎng)絡(luò)
?
三、如何避免GIL的影響
為了利用多核資源,又能避免GIL對多線程性能的影響,這里介紹一下Python社區(qū)現(xiàn)有的解決方案:
1、使用C擴(kuò)展
這種方案的主要思想是將計(jì)算密集型任務(wù)轉(zhuǎn)移給C,跟Python獨(dú)立,在工作的時(shí)候在C代碼中釋放GIL。這樣在計(jì)算密集的時(shí)候程序執(zhí)行速度也會(huì)提升的很快。例如你要操作數(shù)組,那么使用NumPy這樣的擴(kuò)展會(huì)非常的高效。但是同樣帶來不好的問題就是python需要依賴很多C擴(kuò)展,喪失了一部分簡單易用的特性。
2、使用 multiprocessing 庫實(shí)現(xiàn)多進(jìn)程
Python的多線程雖然是單核多線程,但是多進(jìn)程卻是可以真正利用多核資源的,多個(gè)Python進(jìn)程有各自獨(dú)立的GIL鎖,互不影響。
為了彌補(bǔ)thread庫因?yàn)镚IL而低效的缺陷,出現(xiàn)了multiprocessing庫。它完整的復(fù)制了一套thread所提供的接口方便遷移。唯一的不同就是它使用了多進(jìn)程而不是多線程。 當(dāng)一個(gè)線程想要執(zhí)行CPU密集型工作時(shí),會(huì)將任務(wù)發(fā)給進(jìn)程池。 然后進(jìn)程池會(huì)在另外一個(gè)進(jìn)程中啟動(dòng)一個(gè)單獨(dú)的Python解釋器來工作,這時(shí)候原本的線程相當(dāng)于執(zhí)行I/O操作,當(dāng)線程等待I/O結(jié)果的時(shí)候會(huì)釋放GIL。
這種方式也有缺陷,當(dāng)混合使用線程和進(jìn)程池的時(shí)候會(huì)增加程序?qū)崿F(xiàn)時(shí)線程間數(shù)據(jù)通訊和同步的困難。所以最好在程序啟動(dòng)時(shí),創(chuàng)建任何線程之前先創(chuàng)建一個(gè)單例的進(jìn)程池。 然后線程使用同樣的進(jìn)程池來進(jìn)行它們的計(jì)算密集型工作。
3、使用其他解析器
像JPython等解析器,不需要GIL的幫助。但是由于這類是采用Java或C#等語言用于解析器實(shí)現(xiàn),他們也失去了利用社區(qū)眾多C語言模塊有用特性的機(jī)會(huì),許多C擴(kuò)展的庫無法使用。關(guān)于其他解析器,也可以了解下PyPy。
?
四、總結(jié)歸納
Python GIL其實(shí)是歷史遺留問題,跟我們常常做算法是要舍棄空間還是舍棄時(shí)間是一個(gè)道理,是性能與python簡便性之間權(quán)衡后的產(chǎn)物:
1、具有很多CPU計(jì)算密集型的程序,GIL只能讓多線程以當(dāng)個(gè)線程的形式運(yùn)行,效率低下;
2、如果只是I/O密集型的操作,多線程能在一定程度上提升程序效率;
3、使用并行程序時(shí)為了避免GIL的影響,可以使用多進(jìn)程、C擴(kuò)展等等方式解決。
參考鏈接:http://cenalulu.github.io/python/gil-in-python/
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

