自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

最令人頭疼的Python問題

開發(fā) 后端
Python中由于使用了全局解釋鎖(GIL)的原因,代碼并不能同時(shí)在多核上并發(fā)的運(yùn)行,也就是說,Python的多線程不能并發(fā),很多人會(huì)發(fā)現(xiàn)使用多線程來改進(jìn)自己的Python代碼后,程序的運(yùn)行效率卻下降了。這篇文章對(duì)Python中的全局解釋鎖(GIL)進(jìn)行了介紹。作者認(rèn)為這是Python中最令人頭疼的問題。

Python中由于使用了全局解釋鎖(GIL)的原因,代碼并不能同時(shí)在多核上并發(fā)的運(yùn)行,也就是說,Python的多線程不能并發(fā),很多人會(huì)發(fā)現(xiàn)使用多線程來改進(jìn)自己的Python代碼后,程序的運(yùn)行效率卻下降了。這篇文章對(duì)Python中的全局解釋鎖(GIL)進(jìn)行了介紹。作者認(rèn)為這是Python中最令人頭疼的問題。

十年多年來,Python 的全局解釋器鎖(GIL)給新手和專家們帶來了巨大的挫折感和好奇心。

[[207057]]

懸而未決的問題

每個(gè)領(lǐng)域都會(huì)有這么一個(gè)問題:它難度大、耗時(shí)多,僅僅是嘗試解決這個(gè)問題都會(huì)讓人震驚。整個(gè)社區(qū)在很久以前就放棄了這個(gè)問題,現(xiàn)在只有少數(shù)人在努力試圖解決它。對(duì)于初學(xué)者來說,解決這樣高難度的問題,會(huì)給他帶來足夠的聲譽(yù)。計(jì)算機(jī)科學(xué)領(lǐng)域中的 P = NP 就是這樣的問題。如果能用多項(xiàng)式時(shí)間復(fù)雜度解決這個(gè)問題,那簡直就可以改變世界了。Python 中最困難的問題比 P = NP 要容易一些,不過迄今仍然沒有一個(gè)滿意的答案,解決這個(gè)問題和解決 P = NP 問題一樣具有革命性。正因?yàn)槿绱耍?Python 社區(qū)會(huì)有如此多的人關(guān)注于這個(gè)的問題: “對(duì)于全局解釋器鎖(GIL)能做什么?”

Python 的底層

要理解 GIL 的含義,我們需要從 Python 的基礎(chǔ)說起。像 C++ 這樣的語言屬于編譯型語言,顧名思義,該類型語言的代碼輸入到編譯器,由編譯器根據(jù)語言的語法進(jìn)行解析,生成與語言無關(guān)的中間表示,***鏈接成由高度優(yōu)化的機(jī)器碼組成的可執(zhí)行程序。因?yàn)榫幾g器可以獲取全部代碼(或者是一大段相對(duì)獨(dú)立的代碼),所以編譯器可以對(duì)代碼進(jìn)行深度優(yōu)化。這使得它可以對(duì)不同的語言結(jié)構(gòu)之間的交互進(jìn)行推理,從而做出更有效的優(yōu)化。

相反,Python 是解釋型語言。代碼被輸入到解釋器來運(yùn)行。解釋器在執(zhí)行之前對(duì)代碼一無所知;它只知道 Python 的規(guī)則,以及如何在執(zhí)行過程中動(dòng)態(tài)地應(yīng)用這些規(guī)則。它也有一些優(yōu)化,但是和編譯型語言的優(yōu)化完全不同。由于解釋器不能很好地對(duì)代碼進(jìn)行推導(dǎo),Python 的大部分優(yōu)化其實(shí)是解釋器本身的優(yōu)化。更快的解釋器自然意味著更快的程序運(yùn)行速度,而這種優(yōu)化對(duì)開發(fā)者來說是免費(fèi)的。也就是說,解釋器優(yōu)化后,開發(fā)者不用修改 Python 代碼就可以坐享優(yōu)化帶來的好處。

這是非常重要的一點(diǎn),這里有必要在強(qiáng)調(diào)一下。在同等條件下,Python 程序的運(yùn)行速度與解釋器的“速度”直接相關(guān)相關(guān)。無論開發(fā)者怎樣優(yōu)化自己的代碼,程序的執(zhí)行速度還是受限于解釋器的執(zhí)行效率。很明顯,這就是為什么做了如此多的工作去優(yōu)化 Python 解釋器。這大概是離 Python 開發(fā)者最近的免費(fèi)的午餐。

免費(fèi)午餐結(jié)束了

還是沒有結(jié)束?摩爾定律告訴了我們硬件提速的時(shí)間表,同時(shí),整整一代程序員學(xué)會(huì)了如何在摩爾定律下編寫代碼。如果程序員寫了比較慢的代碼,最簡單的辦法通常是稍稍等待一下更快的處理器問世即可。事實(shí)上,摩爾定律仍然是并且會(huì)在很長一段時(shí)間內(nèi)是有效的,不過它生效的方式有了根本的變化。時(shí)鐘頻率不會(huì)穩(wěn)定增長到一個(gè)高不可攀的速度,取而代之的是通過多核來利用晶體管密度提高帶來的好處。想要程序能夠充分利用新處理器的性能,就必須按照并發(fā)方式對(duì)代碼進(jìn)行重寫。

大部分開發(fā)者聽到“并發(fā)”通常會(huì)馬上想到多線程程序。目前,多線程仍是利用多核系統(tǒng)最常見的方式。多線程編程比傳統(tǒng)的“順序”編程要難很多,不過仔細(xì)的程序員可以在代碼中充分利用多線程的并發(fā)性。既然幾乎所有應(yīng)用廣泛的現(xiàn)代編程語言都支持多線程編程,語言在多線程方面的實(shí)現(xiàn)應(yīng)該是事后添加上去的。

意外的事實(shí)

現(xiàn)在我們來看一下問題的癥結(jié)所在。想要利用多核系統(tǒng),Python 必須支持多線程。作為解釋型語言,Python 的解釋器對(duì)多線程的支持必須是既安全又高效的。我們都知道多線程編程帶來的問題。解釋器必須避免不同的線程操作內(nèi)部共享的數(shù)據(jù)。同時(shí)還要保證用戶線程能完成盡量多的計(jì)算。

那么在不同線程同時(shí)訪問數(shù)據(jù)時(shí),怎樣才能保護(hù)數(shù)據(jù)呢?答案是全局解釋器鎖。顧名思義,這是一個(gè)加在解釋器上的全局鎖(從互斥量或者類似意義上來看)。這種方式是很安全,但是(對(duì)于 Python 初學(xué)者來說)這也就意味著:對(duì)于任何 Python 程序,不論有多少線程,多少處理器,任何時(shí)候都只有一個(gè)線程在執(zhí)行。

許多人都是偶然發(fā)現(xiàn)這個(gè)事實(shí)。網(wǎng)上的討論組和留言板充斥著來自 Python 初學(xué)者和專家提出的類似的問題:為什么我全新的多線程 Python 程序運(yùn)行得比其只有一個(gè)線程的時(shí)候還要慢?在問這個(gè)問題時(shí),許多人還覺得自己像個(gè)傻瓜,因?yàn)槿绻绦虼_實(shí)是可并行的,那么兩個(gè)線程的程序顯然要比單線程要快。事實(shí)上,問及這個(gè)問題的次數(shù)實(shí)在太多了,Python 的專家們已經(jīng)為它準(zhǔn)備了一個(gè)標(biāo)準(zhǔn)答案:不要使用多線程,請(qǐng)使用多進(jìn)程。但這個(gè)答案比問題本身更加讓人困惑:難道我不能在 Python 中使用多線程?在 Python 這樣流行的語言中使用多線程究竟是有多糟糕,連專家都建議不要使用。是我哪里沒有搞明白嗎?

很遺憾,并不是。由于 Python 解釋器的設(shè)計(jì),使用多線程以提高性能可以算是一個(gè)困難的任務(wù)。在最壞的情況下,多線程反而會(huì)降低(有時(shí)很明顯)程序的運(yùn)行速度。一個(gè)計(jì)算機(jī)科學(xué)專業(yè)的新生就可以告訴你:當(dāng)多個(gè)線程競爭一個(gè)共享資源時(shí)將會(huì)發(fā)生什么。結(jié)果通常不理想。很多情況下多線程都能很好地工作,對(duì)于解釋器的實(shí)現(xiàn)和內(nèi)核開發(fā)人員來說,不要對(duì) Python 多線程性能有太多抱怨可能是他們***的心愿。

現(xiàn)在該怎么辦呢?慌了嗎?

我們現(xiàn)在能做什么呢?難道作為 Python 開發(fā)人員的我們要放棄使用多線程來實(shí)現(xiàn)并行嗎?為什么 GIL 在某一時(shí)刻只允許一個(gè)線程在運(yùn)行呢?在并發(fā)訪問時(shí),難道不可以用粒度更細(xì)的鎖來保護(hù)多個(gè)獨(dú)立對(duì)象?為什么沒有人做過類似的嘗試呢?

這些問題很實(shí)用,它們的答案也十分有趣。GIL 為很多對(duì)象的訪問提供這保護(hù),比如當(dāng)前線程狀態(tài)和為垃圾回收而用的堆分配對(duì)象。這對(duì) Python 語言來說沒什么奇怪的,它需要使用一個(gè) GIL 。這是該實(shí)現(xiàn)的一種產(chǎn)物?,F(xiàn)在也有不使用 GIL 的 Python 解釋器(和編譯器)。但是對(duì)于 CPython 來說,從其產(chǎn)生到現(xiàn)在 GIL 就一直在存在了。

那么為什么我們不拋棄 GIL 呢?許多人也許不知道,1999年的時(shí)候,Greg Stein 針對(duì) Python 1.5 提交了一個(gè)名為“free threading”的補(bǔ)丁,這個(gè)補(bǔ)丁經(jīng)常被提到卻不怎么被人理解。這個(gè)補(bǔ)丁就嘗試了將 GIL 完全移除,并用細(xì)粒度的鎖來代替。然而,GIL 移除的代價(jià)是單線程程序的執(zhí)行速度下降,下降的幅度大概有 40%。使用兩個(gè)線程可以讓速度有所提升,但是速度的提升并沒有隨著核數(shù)的增加而線性增長。由于執(zhí)行速度的降低,這一補(bǔ)丁沒有被接受了,并且?guī)缀醣蝗诉z忘。

GIL 讓人頭痛,我們還是想點(diǎn)其他辦法吧

盡管“free threading”這個(gè)補(bǔ)丁沒有被接受,但是它還是有啟發(fā)性意義。它證明了一個(gè)關(guān)于 Python 解釋器的基本要點(diǎn):移除 GIL 是非常困難的。比起該補(bǔ)丁發(fā)布的時(shí)候,現(xiàn)在的解釋器依賴的全局狀態(tài)變得更多了,這使得移除 GIL 變得更加困難。值得一提的是,也正是因?yàn)檫@個(gè)原因,許多人對(duì)移除 GIL 變得更感興趣了。困難的問題通常都很有趣。

但是這可能有點(diǎn)被誤導(dǎo)了。我們假設(shè)一下:如果我們有這樣一個(gè)神奇的補(bǔ)丁,它其移除了 GIL ,并且沒有使單線程的 Python 代碼性能下降,我們會(huì)得到一直想要的東西:一個(gè)能并發(fā)使用所有處理器的線程 API?,F(xiàn)在我們已經(jīng)獲得了我們希望的,但這確實(shí)是件好事嗎?

基于線程的編程是困難的。當(dāng)一個(gè)人覺得自己了解關(guān)于線程的一切,總會(huì)有一些新問題出現(xiàn)。一些非常知名的語言設(shè)計(jì)者和研究者站出來反對(duì)線程模型,因?yàn)樵谶@方面想要得到合理的一致性真的是太難了。就像任何一個(gè)寫過多線程應(yīng)用程序的人可以告訴你的一樣,不管是多線程應(yīng)用的開發(fā)還是調(diào)試難度都會(huì)是單線程的應(yīng)用的指數(shù)倍。程序員的思維模型往往適應(yīng)順序執(zhí)行模型,恰恰與并行執(zhí)行模型不匹配。GIL 的出現(xiàn)無意中幫助了開發(fā)者免于陷入困境。在使用多線程時(shí)仍然需要同步原語,GIL 事實(shí)上幫助我們保證不同線程之間的數(shù)據(jù)一致性。

這么說起來 Python 最難的問題似乎有點(diǎn)問錯(cuò)了問題。Python 專家推薦使用多進(jìn)程代替多線程是有道理的,而不是想要給 Python 線程實(shí)現(xiàn)遮羞。Python 的這種實(shí)現(xiàn)方式促使開發(fā)者使用更安全也更直觀的方式實(shí)現(xiàn)并發(fā)模型,同時(shí)保留使用多線程進(jìn)行開發(fā),讓開發(fā)者在必要的時(shí)候使用。大多數(shù)人可能并不清楚什么是***的并行編程模型。但是大多數(shù)人都清楚多線程的方式并不是***的并行模型。

不要認(rèn)為 GIL 是一成不變或者毫無道理的。Antoine Pitrou 在 Python 3.2 中實(shí)現(xiàn)了一個(gè)新的 GIL ,比較顯著地改進(jìn)的 Python 解釋器。這是1992年以來,針對(duì) GIL 最主要的一次改進(jìn)。這個(gè)改變非常巨大,很難在這里解釋清楚,但是從高層次來看,舊的 GIL 通過對(duì) Python 指令進(jìn)行計(jì)數(shù)來確定何時(shí)釋放 GIL。由于 Python 指令和翻譯成的機(jī)器指令并非一一對(duì)應(yīng)的關(guān)系,這使得單條 Python 指令可能包含大量工作。新的 GIL 用一個(gè)固定的超時(shí)時(shí)間來指示當(dāng)前的線程釋放鎖。在當(dāng)前線程持有鎖且第二個(gè)線程請(qǐng)求這個(gè)鎖的時(shí)候,當(dāng)前線程就會(huì)在 5 ms 后被強(qiáng)制釋放這個(gè)鎖(這就是說,當(dāng)前線程每 5 ms 就要檢查其是否需要釋放這個(gè)鎖)。在任務(wù)可以執(zhí)行的情況下,這使得預(yù)測(cè)線程間的切換變得更容易。

然而,這并不是一個(gè)***的改進(jìn)。對(duì)于不同類型任務(wù)執(zhí)行過程中 GIL 的作用的研究,David Beazley 可能是最活躍的一個(gè)。除了對(duì) Python 3.2 之前的 GIL 研究最深入,他還研究了這個(gè)***的 GIL 實(shí)現(xiàn),并且發(fā)現(xiàn)了很多有趣的程序方案:在這些方案中,即使是新的 GIL 實(shí)現(xiàn),表現(xiàn)也相當(dāng)糟糕。他目前仍然通過實(shí)踐研究來推動(dòng)著有關(guān) GIL 的討論,并發(fā)布實(shí)踐結(jié)果。

不管人們對(duì) Python 的 GIL 看法如何,它仍然是 Python 語言里最困難的技術(shù)挑戰(zhàn)。想要理解它的實(shí)現(xiàn)需要對(duì)操作系統(tǒng)設(shè)計(jì)、多線程編程、C 語言、解釋器設(shè)計(jì)和 CPython 解釋器的實(shí)現(xiàn)有著非常透徹的理解。單是這些前提就妨礙了很多開發(fā)者去更徹底地研究 GIL。然而并沒有任何跡象表明 GIL 會(huì)在不久之后遠(yuǎn)離我們。目前,它將繼續(xù)給那些新接觸 Python 并對(duì)解決技術(shù)難題感興趣的人帶來困惑和驚喜。

以上內(nèi)容是基于我目前對(duì) Python 解釋器的研究。我打算寫一些關(guān)于解釋器其它方面的內(nèi)容,但是沒有比 GIL 知名度更高的了。雖然這些技術(shù)細(xì)節(jié)來自我對(duì) CPython 代碼庫的徹底研究,但是仍有可能存在不準(zhǔn)確的地方。如果你發(fā)現(xiàn)了不準(zhǔn)確的內(nèi)容,請(qǐng)及時(shí)告知我,我會(huì)盡快修正。

責(zé)任編輯:未麗燕 來源: 程序師
相關(guān)推薦

2010-11-16 09:07:32

2023-04-19 09:05:44

2010-04-08 13:17:39

IT管理系統(tǒng)遷移賽門鐵克

2017-07-14 14:52:25

MySQLAborted告警案例分析

2021-05-09 22:26:36

Python函數(shù)變量

2022-05-19 12:14:22

分布式開發(fā)框架

2016-09-23 15:17:27

2011-06-20 14:42:45

2009-08-19 11:39:38

ASP.NET Rou

2013-01-23 10:20:48

2020-04-10 14:15:27

Ubuntu 20.0Linux命令

2021-10-09 09:47:14

Java開發(fā) bug

2023-09-07 16:23:22

2019-07-03 15:01:30

戴爾

2017-12-12 13:27:20

主板跳線USB

2017-12-21 04:31:38

物聯(lián)網(wǎng)技術(shù)趨勢(shì)

2010-05-17 17:54:17

微軟蘋果失敗

2010-03-01 13:25:43

2020-02-03 17:22:34

垃圾回收原理種類

2009-04-02 10:23:40

福布斯期待游戲
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)