说说GIL
上一篇:線程深入篇引入
Code:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Thread/3.GIL
說(shuō)說(shuō)GIL
盡管Python完全支持多線程編程, 但是解釋器的C語(yǔ)言實(shí)現(xiàn)部分在完全并行執(zhí)行時(shí)并不是線程安全的,所以這時(shí)候才引入了GIL
解釋器被一個(gè)全局解釋器鎖保護(hù)著,它確保任何時(shí)候都只有一個(gè)Python線程執(zhí)行(保證C實(shí)現(xiàn)部分能線程安全) GIL最大的問(wèn)題就是Python的多線程程序并不能利用多核CPU的優(yōu)勢(shì) (比如一個(gè)使用了多個(gè)線程的計(jì)算密集型程序只會(huì)在一個(gè)單CPU上面運(yùn)行)
注意:GIL只會(huì)影響到那些嚴(yán)重依賴CPU的程序(比如計(jì)算型的)如果你的程序大部分只會(huì)涉及到I/O,比如網(wǎng)絡(luò)交互,那么使用多線程就很合適 ~ 因?yàn)樗鼈兇蟛糠謺r(shí)間都在等待(線程被限制到同一時(shí)刻只允許一個(gè)線程執(zhí)行這樣一個(gè)執(zhí)行模型。GIL會(huì)根據(jù)執(zhí)行的字節(jié)碼行數(shù)和時(shí)間片來(lái)釋放GIL,在遇到IO操作的時(shí)候會(huì)主動(dòng)釋放權(quán)限給其他線程)
所以Python的線程更適用于處理I/O和其他需要并發(fā)執(zhí)行的阻塞操作,而不是需要多處理器并行的計(jì)算密集型任務(wù)(對(duì)于IO操作來(lái)說(shuō),多進(jìn)程和多線程性能差別不大)【計(jì)算密集現(xiàn)在可以用Python的Ray框架】
網(wǎng)上摘取一段關(guān)于IO密集和計(jì)算密集的說(shuō)明:(IO密集型可以結(jié)合異步)
計(jì)算密集型任務(wù)的特點(diǎn)是要進(jìn)行大量的計(jì)算,消耗CPU資源,比如計(jì)算圓周率、對(duì)視頻進(jìn)行高清解碼等等,全靠CPU的運(yùn)算能力。這種計(jì)算密集型任務(wù)雖然也可以用多任務(wù)完成,但是任務(wù)越多,花在任務(wù)切換的時(shí)間就越多,CPU執(zhí)行任務(wù)的效率就越低,所以,要最高效地利用CPU,計(jì)算密集型任務(wù)同時(shí)進(jìn)行的數(shù)量應(yīng)當(dāng)?shù)扔贑PU的核心數(shù)。計(jì)算密集型任務(wù)由于主要消耗CPU資源,因此,代碼運(yùn)行效率至關(guān)重要。Python這樣的腳本語(yǔ)言運(yùn)行效率很低,完全不適合計(jì)算密集型任務(wù)。對(duì)于計(jì)算密集型任務(wù),最好用C語(yǔ)言編寫(xiě)。第二種任務(wù)的類型是IO密集型,涉及到網(wǎng)絡(luò)、磁盤(pán)IO的任務(wù)都是IO密集型任務(wù),這類任務(wù)的特點(diǎn)是CPU消耗很少,任務(wù)的大部分時(shí)間都在等待IO操作完成(因?yàn)镮O的速度遠(yuǎn)遠(yuǎn)低于CPU和內(nèi)存的速度)。對(duì)于IO密集型任務(wù),任務(wù)越多,CPU效率越高,但也有一個(gè)限度。常見(jiàn)的大部分任務(wù)都是IO密集型任務(wù),比如Web應(yīng)用。IO密集型任務(wù)執(zhí)行期間,99%的時(shí)間都花在IO上,花在CPU上的時(shí)間很少,因此,用運(yùn)行速度極快的C語(yǔ)言替換用Python這樣運(yùn)行速度極低的腳本語(yǔ)言,完全無(wú)法提升運(yùn)行效率。對(duì)于IO密集型任務(wù),最合適的語(yǔ)言就是開(kāi)發(fā)效率最高(代碼量最少)的語(yǔ)言,腳本語(yǔ)言是首選,C語(yǔ)言最差。Process and Thread Test
其實(shí)用不用多進(jìn)程看你需求,不要麻木使用,Linux下還好點(diǎn),Win下進(jìn)程開(kāi)銷就有點(diǎn)大了(好在服務(wù)器基本上都是Linux,程序員開(kāi)發(fā)環(huán)境也大多Linux了)這邊只是簡(jiǎn)單測(cè)了個(gè)啟動(dòng)時(shí)間差距就來(lái)了,其他的都不用測(cè)試了
測(cè)試Code:
from time import sleep from multiprocessing import Processdef test(i):sleep(1)print(i)def main():t_list = [Process(target=test, args=(i, )) for i in range(1000)]for t in t_list:t.start()if __name__ == '__main__':main()運(yùn)行時(shí)間:
real 0m3.980s user 0m2.034s sys 0m3.119s操作系統(tǒng)幾千個(gè)進(jìn)程開(kāi)銷還是有點(diǎn)大的(畢竟進(jìn)程是有上線的)ulimit -a
測(cè)試Code:
from time import sleep from multiprocessing.dummy import Processdef test(i):sleep(1)print(i)def main():t_list = [Process(target=test, args=(i, )) for i in range(1000)]for t in t_list:t.start()if __name__ == '__main__':main()運(yùn)行時(shí)間:
real 0m1.130s user 0m0.158s sys 0m0.095smultiprocessing.dummy里面的Process上面也說(shuō)過(guò)了,就是在線程基礎(chǔ)上加點(diǎn)東西使得用起來(lái)和multiprocessing的Process編程風(fēng)格基本一致(本質(zhì)還是線程)
測(cè)試Code:
from time import sleep from multiprocessing.dummy import threadingdef test(i):sleep(1)print(i)def main():t_list = [threading.Thread(target=test, args=(i, )) for i in range(1000)]for t in t_list:t.start()if __name__ == '__main__':main()運(yùn)行時(shí)間:
real 0m1.123s user 0m0.154s sys 0m0.085s其實(shí)Redis就是使用單線程和多進(jìn)程的經(jīng)典,它的性能有目共睹。所謂性能無(wú)非看個(gè)人能否充分發(fā)揮罷了。不然就算給你轟炸機(jī)你也不會(huì)開(kāi)啊?扎心不老鐵~
PS:線程和進(jìn)程各有其好處,無(wú)需一棍打死,具體啥好處可以回顧之前寫(xiě)的進(jìn)程和線程篇~
利用共享庫(kù)來(lái)擴(kuò)展
C系擴(kuò)展
GIL是Python解釋器設(shè)計(jì)的歷史遺留問(wèn)題,多線程編程,模型復(fù)雜,容易發(fā)生沖突,必須用鎖加以隔離,同時(shí),又要小心死鎖的發(fā)生。Python解釋器由于設(shè)計(jì)時(shí)有GIL全局鎖,導(dǎo)致了多線程無(wú)法利用多核。計(jì)算密集型任務(wù)要真正利用多核,除非重寫(xiě)一個(gè)不帶GIL的解釋器(PyPy)如果一定要通過(guò)多線程利用多核,可以通過(guò)C擴(kuò)展來(lái)實(shí)現(xiàn)(Python很多模塊都是用C系列寫(xiě)的,所以用C擴(kuò)展也就不那么奇怪了)
只要用C系列寫(xiě)個(gè)簡(jiǎn)單功能(不需要深入研究高并發(fā)),然后使用ctypes導(dǎo)入使用就行了:
#include <stdio.h> void test() { while(1){} }編譯成共享庫(kù):gcc 2.test.c -shared -o libtest.so
使用Python運(yùn)行指定方法:(太方便了,之前一直以為C#調(diào)用C系列最方便,用完P(guān)ython才知道更簡(jiǎn)方案)
from ctypes import cdll from os import cpu_count from multiprocessing.dummy import Pooldef main():# 加載C共享庫(kù)(動(dòng)態(tài)鏈接庫(kù))lib = cdll.LoadLibrary("./libtest.so")pool = Pool() # 默認(rèn)是系統(tǒng)核數(shù)pool.map_async(lib.test, range(cpu_count()))pool.close()pool.join()if __name__ == '__main__':main()看看這時(shí)候HTOP的信息:(充分利用多核)【ctypes在調(diào)用C時(shí)會(huì)自動(dòng)釋放GIL】
Go擴(kuò)展
利用Go寫(xiě)個(gè)死循環(huán),然后編譯成so動(dòng)態(tài)鏈接庫(kù)(共享庫(kù)):
package main import "C"//export test func test(){for true{} }func main() {test() }非常重要的事情://export test一定要寫(xiě),不然就被自動(dòng)改成其他名字(我當(dāng)時(shí)被坑過(guò))
Python調(diào)用和上面一樣:
from ctypes import cdll from os import cpu_count from multiprocessing.dummy import Pooldef main():# 加載動(dòng)態(tài)鏈接庫(kù)lib = cdll.LoadLibrary("./libtestgo.so")pool = Pool() # 默認(rèn)是系統(tǒng)核數(shù)pool.map_async(lib.test, range(cpu_count()))pool.close()pool.join()if __name__ == '__main__':main()效果:go build -buildmode=c-shared -o libtestgo.so 2.test.go
題外話~如果想等CPython的GIL消失可以先看一個(gè)例子:MySQL把大鎖改成各個(gè)小鎖花了5年。在是在MySQL有專門(mén)的團(tuán)隊(duì)和公司前提下,而Python完全靠社區(qū)重構(gòu)就太慢了
速度方面微軟除外,更新快本來(lái)是好事,但是動(dòng)不動(dòng)斷層更新,這學(xué)習(xí)成本就太大了(這也是為什么Net能深入的人比較少的原因:人家剛深入一個(gè),你就淘汰一個(gè)了...)
可能還有人不清楚,貼下官方推薦技術(shù)吧(NetCore、Orleans、EFCore、ML.Net、CoreRT)
https://github.com/aspnet/AspNetCorehttps://github.com/aspnet/EntityFrameworkCorehttps://github.com/dotnet/machinelearninghttps://github.com/dotnet/orleanshttps://github.com/aspnet/Mvchttps://github.com/dotnet/corert課外拓展:
用go語(yǔ)言給python3開(kāi)發(fā)模塊 https://www.jianshu.com/p/40e069954804 https://blog.filippo.io/building-python-modules-with-go-1-5Python與C/C++相互調(diào)用 https://www.cnblogs.com/apexchu/p/5015961.html使用C/C++代碼編寫(xiě)Python模塊 https://www.cnblogs.com/silvermagic/p/9087896.html快速實(shí)現(xiàn)python c擴(kuò)展模塊 https://www.cnblogs.com/chengxuyuancc/p/6374239.htmlPython的C語(yǔ)言擴(kuò)展 https://python3-cookbook.readthedocs.io/zh_CN/latest/chapters/p15_c_extensions.htmlpython調(diào)用golang生成的so庫(kù) https://studygolang.com/articles/10228 https://www.cnblogs.com/huangguifeng/p/8931837.htmlpython調(diào)用golang并回調(diào) https://blog.csdn.net/gtd138/article/details/79801235Python3.x AttributeError: libtest.so: undefined symbol: fact https://www.cnblogs.com/tanglizi/p/8965230.html運(yùn)行在其他編譯器上
先看最重要的一點(diǎn),一旦運(yùn)行在其他編譯器意味著很多Python第三方庫(kù)可能就不能用了,相對(duì)來(lái)說(shuō)PyPy兼容性是最好的了
如果是Python2系列我推薦谷歌的grumpy
Grumpy是一個(gè) Python to Go 源代碼轉(zhuǎn)換編譯器和運(yùn)行時(shí)。旨在成為CPython2.7的近乎替代品。關(guān)鍵的區(qū)別在于它將Python源代碼編譯為Go源代碼,然后將其編譯為本機(jī)代碼,而不是字節(jié)碼。這意味著Grumpy沒(méi)有VM已編譯的Go源代碼是對(duì)Grumpy運(yùn)行時(shí)的一系列調(diào)用,Go庫(kù)提供與 Python C API類似的目的如果是Python3系列,可以使用PyPy PythonNet Jython3 ironpython3等等
PyPy:https://bitbucket.org/pypy/pypy
Net方向:
https://github.com/pythonnet/pythonnet https://github.com/IronLanguages/ironpython3Java方向:
https://github.com/jython/jython3Other:
源碼:https://github.com/sbinet/go-python 參考:https://studygolang.com/articles/13019可惜CoreRT一直沒(méi)完善,不然就Happy了 https://github.com/dotnet/corert經(jīng)驗(yàn):平時(shí)基本上多線程就夠用了,如果想多核利用-多進(jìn)程基本上就搞定了(分布式走起)實(shí)在不行一般都是分析一下性能瓶頸在哪,然后寫(xiě)個(gè)擴(kuò)展庫(kù)
如果需要和其他平臺(tái)交互才考慮上面說(shuō)的這些項(xiàng)目。如果是Web項(xiàng)目就更不用擔(dān)心了,現(xiàn)在哪個(gè)公司還不是混用?JavaScript and Python and Go or Java or NetCore。基本上上點(diǎn)規(guī)模的公司都會(huì)用到Python,之前都是Python and Java搭配使用,這幾年開(kāi)始慢慢變成Python and Go or NetCore搭配使用了~
下集預(yù)估:Actor模型 and 消息發(fā)布/訂閱模型
轉(zhuǎn)載于:https://www.cnblogs.com/dunitian/p/9780821.html
總結(jié)
- 上一篇: 长这么大了,一件事也没做好过
- 下一篇: nginx优化-nginx事件处理模型优