亚洲熟女综合色一区二区三区,亚洲精品中文字幕无码蜜桃,亚洲va欧美va日韩va成人网,亚洲av无码国产一区二区三区,亚洲精品无码久久久久久久

Python 多線程居然是 —— 假的?

最近有位讀者提問(wèn):

Python 的多線程真是假的嗎?

一下子點(diǎn)到了 Python 長(zhǎng)期被人們喜憂參半的特性 —— GIL 上了。

到底是怎么回事呢?今天我們來(lái)聊一聊。

十全十美

我們知道 Python 之所以靈活和強(qiáng)大,是因?yàn)樗且粋€(gè)解釋性語(yǔ)言,邊解釋邊執(zhí)行,實(shí)現(xiàn)這種特性的標(biāo)準(zhǔn)實(shí)現(xiàn)叫作 CPython。

它分兩步來(lái)運(yùn)行 Python 程序:

  • 首先解析源代碼文本,并將其編譯為字節(jié)碼(bytecode)[1]
  • 然后采用基于棧的解釋器來(lái)運(yùn)行字節(jié)碼
  • 不斷循環(huán)這個(gè)過(guò)程,直到程序結(jié)束或者被終止

靈活性有了,但是為了保證程序執(zhí)行的穩(wěn)定性,也付出了巨大的代價(jià):

引入了?全局解釋器鎖?GIL(global interpreter lock)[2]

以保證同一時(shí)間只有一個(gè)字節(jié)碼在運(yùn)行,這樣就不會(huì)因?yàn)闆](méi)用事先編譯,而引發(fā)資源爭(zhēng)奪和狀態(tài)混亂的問(wèn)題了。

看似 “十全十美” ,但,這樣做,就意味著多線程執(zhí)行時(shí),會(huì)被 GIL 變?yōu)閱尉€程,無(wú)法充分利用硬件資源。

來(lái)看代碼:

Python 多線程居然是 —— 假的?
  • 函數(shù)?gcd?用于求解最大公約數(shù),用來(lái)模擬一個(gè)數(shù)據(jù)操作
  • NUMBERS?為待求解的數(shù)據(jù)
  • 求解方式利用?map?方法,傳入處理函數(shù) gcd, 和待求解數(shù)據(jù),將返回一個(gè)結(jié)果數(shù)列,最后轉(zhuǎn)化為?list
  • 將執(zhí)行過(guò)程的耗時(shí)計(jì)算并打印出來(lái)

在筆者的電腦上(4核,16G)執(zhí)行時(shí)間為 2.043 秒。

如何換成多線程呢?

Python 多線程居然是 —— 假的?
  • 這里引入了?concurrent.futures?模塊中的線程池,用線程池實(shí)現(xiàn)起來(lái)比較方便
  • 設(shè)置線程池為 4,主要是為了和 CPU 的核數(shù)匹配
  • 線程池?pool?提供了多線程版的?map,所以參數(shù)不變

看看運(yùn)行效果:

Python 多線程居然是 —— 假的?

Python 多線程居然是 —— 假的?

并行執(zhí)行的時(shí)間竟然更長(zhǎng)了!

連續(xù)執(zhí)行多次,結(jié)果都是一樣的,也就是說(shuō)在 GIL 的限制下,多線程是無(wú)效的,而且因?yàn)榫€程調(diào)度還多損耗了些時(shí)間。

戴著鐐銬跳舞

 

難道 Python 里的多線程真的沒(méi)用嗎?

其實(shí)也并不是,雖然了因?yàn)?GIL,無(wú)法實(shí)現(xiàn)真正意義上的多線程,但,多線程機(jī)制,還是為我們提供了兩個(gè)重要的特性。

一:多線程寫(xiě)法可以讓某些程序更好寫(xiě)

怎么理解呢?

如果要解決一個(gè)需要同時(shí)維護(hù)多種狀態(tài)的程序,用單線程是實(shí)現(xiàn)是很困難的。

比如要檢索一個(gè)文本文件中的數(shù)據(jù),為了提高檢索效率,可以將文件分成小段的來(lái)處理,最先在那段中找到了,就結(jié)束處理過(guò)程。

用單線程的話,很難實(shí)現(xiàn)同時(shí)兼顧多個(gè)分段的情況,只能順序,或者用二分法執(zhí)行檢索任務(wù)。

而采用多線程,可以將每個(gè)分段交給每個(gè)線程,會(huì)輪流執(zhí)行,相當(dāng)于同時(shí)推薦檢索任務(wù),處理起來(lái),效率會(huì)比順序查找大大提高。

二:處理阻塞型 I/O 任務(wù)效率更高

阻塞型 I/O 的意思是,當(dāng)系統(tǒng)需要與文件系統(tǒng)(也包括網(wǎng)絡(luò)和終端顯示)交互時(shí),由于文件系統(tǒng)相比于 CPU 的處理速度慢得多,所以程序會(huì)被設(shè)置為阻塞狀態(tài),即,不再被分配計(jì)算資源。

直到文件系統(tǒng)的結(jié)果返回,才會(huì)被激活,將有機(jī)會(huì)再次被分配計(jì)算資源。

也就是說(shuō),處于阻塞狀態(tài)的程序,會(huì)一直等著。

那么如果一個(gè)程序是需要不斷地從文件系統(tǒng)讀取數(shù)據(jù),處理后在寫(xiě)入,單線程的話就需要等等讀取后,才能處理,等待處理完才能寫(xiě)入,于是處理過(guò)程就成了一個(gè)個(gè)的等待。

而用多線程,當(dāng)一個(gè)處理過(guò)程被阻塞之后,就會(huì)立即被 GIL 切走,將計(jì)算資源分配給其他可以執(zhí)行的過(guò)程,從而提示執(zhí)行效率。

有了這兩個(gè)特性,就說(shuō)明 Python 的多線程并非一無(wú)是處,如果能根據(jù)情況編寫(xiě)好,效率會(huì)大大提高,只不過(guò)對(duì)于計(jì)算密集型的任務(wù),多線程特性愛(ài)莫能助。

曲線救國(guó)

那么有沒(méi)有辦法,真正的利用計(jì)算資源,而不受 GIL 的束縛呢?

當(dāng)然有,而且還不止一個(gè)。

先介紹一個(gè)簡(jiǎn)單易用的方式。

回顧下前面的計(jì)算最大公約數(shù)的程序,我們用了線程池來(lái)處理,不過(guò)沒(méi)用效果,而且比不用更糟糕。

這是因?yàn)檫@個(gè)程序是計(jì)算密集型的,主要依賴(lài)于 CPU,顯然會(huì)受到 GIL 的約束。

現(xiàn)在我們將程序稍作修改:

Python 多線程居然是 —— 假的?

看看效果:

Python 多線程居然是 —— 假的?

并行執(zhí)行提升了將近 3 倍!什么情況?

仔細(xì)看下,主要是將多線程中的?ThreadPoolExecutor?換成了?ProcessPoolExecutor,即進(jìn)程池執(zhí)行器。

在同一個(gè)進(jìn)程里的 Python 程序,會(huì)受到 GIL 的限制,但不同的進(jìn)程之間就不會(huì)了,因?yàn)槊總€(gè)進(jìn)程中的 GIL 是獨(dú)立的。

是不是很神奇?這里,多虧了?concurrent.futures?模塊將實(shí)現(xiàn)進(jìn)程池的復(fù)雜度封裝起來(lái)了,留給我們簡(jiǎn)潔優(yōu)雅的接口。

這里需要注意的是,ProcessPoolExecutor?并非萬(wàn)能的,它比較適合于?數(shù)據(jù)關(guān)聯(lián)性低,且是?計(jì)算密集型?的場(chǎng)景。

如果數(shù)據(jù)關(guān)聯(lián)性強(qiáng),就會(huì)出現(xiàn)進(jìn)程間 “通信” 的情況,可能使好不容易換來(lái)的性能提升化為烏有。

處理進(jìn)程池,還有什么方法呢?那就是:

用 C 語(yǔ)言重寫(xiě)一遍需要提升性能的部分

不要驚愕,Python 里已經(jīng)留好了針對(duì) C 擴(kuò)展的 API。

但這樣做需要付出更多的代價(jià),為此還可以借助于?SWIG[3]?以及?CLIF[4]?等工具,將 python 代碼轉(zhuǎn)為 C。

有興趣的讀者可以研究一下。

自強(qiáng)不息

 

了解到 Python 多線程的問(wèn)題和解決方案,對(duì)于鐘愛(ài) Python 的我們,何去何從呢?

有句話用在這里很合適:

求人不如求己

哪怕再怎么厲害的工具或者武器,都無(wú)法解決所有的問(wèn)題,而問(wèn)題之所以能被解決,主要是因?yàn)槲覀兊闹饔^能動(dòng)性。

對(duì)情況進(jìn)行分析判斷,選擇合適的解決方案,不就是需要我們做的么?

對(duì)于 Python 中 多線程的詬病,我們更多的是看到它陽(yáng)光和美的一面,而對(duì)于需要提升速度的地方,采取合適的方式。這里簡(jiǎn)單總結(jié)一下:

  1. I/O 密集型的任務(wù),采用 Python 的多線程完全沒(méi)用問(wèn)題,可以大幅度提高執(zhí)行效率
  2. 對(duì)于計(jì)算密集型任務(wù),要看數(shù)據(jù)依賴(lài)性是否低,如果低,采用?ProcessPoolExecutor?代替多線程處理,可以充分利用硬件資源
  3. 如果數(shù)據(jù)依賴(lài)性高,可以考慮將關(guān)鍵的地方改用 C 來(lái)實(shí)現(xiàn),一方面 C 本身比 Python 更快,另一方面,C 可以之間使用更底層的多線程機(jī)制,而完全不用擔(dān)心受 GIL 的影響
  4. 大部分情況下,對(duì)于只能用多線程處理的任務(wù),不用太多考慮,之間利用 Python 的多線程機(jī)制就好了,不用考慮太多

總結(jié)

沒(méi)用十全十美的解決方案,如果有,也只能是在某個(gè)具體的條件之下,就像軟件工程中,沒(méi)用銀彈一樣。

面對(duì)真實(shí)的世界,只有我們自己是可以依靠的,我們通過(guò)學(xué)習(xí)了解更多,通過(guò)實(shí)踐,感受更多,通過(guò)總結(jié)復(fù)盤(pán),收獲更多,通過(guò)思考反思,解決更多。這就是我們?nèi)祟?lèi)不斷發(fā)展前行的原動(dòng)力。

為了我們美好的明天,為了人類(lèi)美好的明天,加油!

比心!

 

相關(guān)新聞

歷經(jīng)多年發(fā)展,已成為國(guó)內(nèi)好評(píng)如潮的Linux云計(jì)算運(yùn)維、SRE、Devops、網(wǎng)絡(luò)安全、云原生、Go、Python開(kāi)發(fā)專(zhuān)業(yè)人才培訓(xùn)機(jī)構(gòu)!