說說GIL
上一篇: ofollow,noindex">執行緒深入篇引入
Code: https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Thread/3.GIL
說說GIL
儘管Python完全支援多執行緒程式設計, 但是直譯器的C語言實現部分在完全並行執行時並不是執行緒安全的,所以這時候才引入了GIL
直譯器被一個全域性直譯器鎖保護著,它確保任何時候都只有一個Python執行緒執行(保證C實現部分能執行緒安全) GIL最大的問題就是Python的多執行緒程式並不能利用多核CPU的優勢 (比如一個使用了多個執行緒的計算密集型程式只會在一個單CPU上面執行)
注意: GIL只會影響到那些嚴重依賴CPU的程式(比如計算型的)如果你的程式大部分只會涉及到I/O,比如網路互動,那麼使用多執行緒就很合適 ~ 因為它們大部分時間都在等待(執行緒被限制到同一時刻只允許一個執行緒執行這樣一個執行模型。GIL會根據執行的位元組碼行數和時間片來釋放GIL,在遇到IO操作的時候會主動釋放許可權給其他執行緒)
所以Python的執行緒 更適用於處理 I/O
和其他需要併發執行的阻塞操作,而不是需要多處理器並行的計算密集型任務 (對於IO操作來說,多程序和多執行緒效能差別不大) 【 計算密集現在可以用Python的 Ray
框架 】
網上摘取一段關於 IO密集和計算密集
的說明:(IO密集型可以結合非同步)
計算密集型任務的特點是要進行大量的計算,消耗CPU資源,比如計算圓周率、對視訊進行高清解碼等等,全靠CPU的運算能力。這種計算密集型任務雖然也可以用多工完成,但是任務越多,花在任務切換的時間就越多,CPU執行任務的效率就越低,所以,要最高效地利用CPU,計算密集型任務同時進行的數量應當等於CPU的核心數。 計算密集型任務由於主要消耗CPU資源,因此,程式碼執行效率至關重要。Python這樣的指令碼語言執行效率很低,完全不適合計算密集型任務。對於計算密集型任務,最好用C語言編寫。 第二種任務的型別是IO密集型,涉及到網路、磁碟IO的任務都是IO密集型任務,這類任務的特點是CPU消耗很少,任務的大部分時間都在等待IO操作完成(因為IO的速度遠遠低於CPU和記憶體的速度)。對於IO密集型任務,任務越多,CPU效率越高,但也有一個限度。常見的大部分任務都是IO密集型任務,比如Web應用。 IO密集型任務執行期間,99%的時間都花在IO上,花在CPU上的時間很少,因此,用執行速度極快的C語言替換用Python這樣執行速度極低的指令碼語言,完全無法提升執行效率。對於IO密集型任務,最合適的語言就是開發效率最高(程式碼量最少)的語言,指令碼語言是首選,C語言最差。
Process and Thread Test
其實用不用多程序看你需求,不要麻木使用,Linux下還好點,Win下程序開銷就有點大了(好在伺服器基本上都是Linux,程式設計師開發環境也大多Linux了)這邊只是簡單測了個啟動時間差距就來了,其他的都不用測試了
測試Code:
from time import sleep from multiprocessing import Process def 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()
執行時間:
real0m3.980s user0m2.034s sys0m3.119s
作業系統幾千個程序開銷還是有點大的(畢竟程序是有上線的) ulimit -a
測試Code:
from time import sleep from multiprocessing.dummy import Process def 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()
執行時間:
real0m1.130s user0m0.158s sys0m0.095s
multiprocessing.dummy
裡面的Process上面也說過了,就是線上程基礎上加點東西使得用起來和 multiprocessing
的 Process
程式設計風格基本一致(本質還是執行緒)
測試Code:
from time import sleep from multiprocessing.dummy import threading def 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()
執行時間:
real0m1.123s user0m0.154s sys0m0.085s
其實Redis就是使用單執行緒和多程序的經典,它的效能有目共睹。所謂效能無非看個人能否充分發揮罷了。不然就算給你轟炸機你也不會開啊?扎心不老鐵~
PS:執行緒和程序各有其好處,無需一棍打死,具體啥好處可以回顧之前寫的程序和執行緒篇~
利用共享庫來擴充套件
C系擴充套件
GIL是Python直譯器設計的歷史遺留問題,多執行緒程式設計,模型複雜,容易發生衝突,必須用鎖加以隔離,同時,又要小心死鎖的發生。Python直譯器由於設計時有GIL全域性鎖,導致了多執行緒無法利用多核。計算密集型任務要真正利用多核,除非重寫一個不帶GIL的直譯器( PyPy
)如果一定要通過多執行緒利用多核,可以通過C擴充套件來實現( Python很多模組都是用C系列寫的,所以用C擴充套件也就不那麼奇怪了 )
只要用C系列寫個簡單功能(不需要深入研究高併發),然後使用 ctypes
匯入使用就行了:
#include <stdio.h> void test() { while(1){} }
編譯成共享庫: gcc 2.test.c -shared -o libtest.so

使用Python執行指定方法:( 太方便了,之前一直以為C#呼叫C系列最方便,用完Python才知道更簡方案
)
from ctypes import cdll from os import cpu_count from multiprocessing.dummy import Pool def main(): # 載入C共享庫(動態連結庫) lib = cdll.LoadLibrary("./libtest.so") pool = Pool()# 預設是系統核數 pool.map_async(lib.test, range(cpu_count())) pool.close() pool.join() if __name__ == '__main__': main()
看看這時候HTOP的資訊:(充分利用多核)【 ctypes在呼叫C時會自動釋放GIL 】

Go擴充套件
利用Go寫個死迴圈,然後編譯成so動態連結庫(共享庫):
package main import "C" //export test func test(){ for true{ } } func main() { test() }
非常重要的事情: //export test
一定要寫,不然就被自動改成其他名字(我當時被坑過)
Python呼叫和上面一樣:
from ctypes import cdll from os import cpu_count from multiprocessing.dummy import Pool def main(): # 載入動態連結庫 lib = cdll.LoadLibrary("./libtestgo.so") pool = Pool()# 預設是系統核數 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消失可以先看一個例子:SQL/">MySQL把大鎖改成各個小鎖花了5年。在是在MySQL有專門的團隊和公司前提下,而Python完全靠社群重構就太慢了
速度方面微軟除外,更新快本來是好事,但是動不動斷層更新,這學習成本就太大了(這也是為什麼Net能深入的人比較少的原因:人家剛深入一個,你就淘汰一個了...)
可能還有人不清楚,貼下官方推薦技術吧( NetCore
、 Orleans
、 EFCore
、 ML.Net
、 CoreRT
)
https://github.com/aspnet/AspNetCore https://github.com/aspnet/EntityFrameworkCore https://github.com/dotnet/machinelearning https://github.com/dotnet/orleans https://github.com/aspnet/Mvc https://github.com/dotnet/corert
課外拓展:
用go語言給python3開發模組 https://www.jianshu.com/p/40e069954804 https://blog.filippo.io/building-python-modules-with-go-1-5 Python與C/C++相互呼叫 https://www.cnblogs.com/apexchu/p/5015961.html 使用C/C++程式碼編寫Python模組 https://www.cnblogs.com/silvermagic/p/9087896.html 快速實現python c擴充套件模組 https://www.cnblogs.com/chengxuyuancc/p/6374239.html Python的C語言擴充套件 https://python3-cookbook.readthedocs.io/zh_CN/latest/chapters/p15_c_extensions.html python呼叫golang生成的so庫 https://studygolang.com/articles/10228 https://www.cnblogs.com/huangguifeng/p/8931837.html python呼叫golang並回調 https://blog.csdn.net/gtd138/article/details/79801235 Python3.x AttributeError: libtest.so: undefined symbol: fact https://www.cnblogs.com/tanglizi/p/8965230.html
執行在其他編譯器上
先看最重要的一點,一旦執行在其他編譯器意味著很多Python第三方庫 可能
就不能用了,相對來說 PyPy
相容性是最好的了
Grumpy是一個 Python to Go 原始碼轉換編譯器和執行時。旨在成為CPython2.7的近乎替代品。關鍵的區別在於它將Python原始碼編譯為Go原始碼,然後將其編譯為本機程式碼,而不是位元組碼。這意味著Grumpy沒有VM 已編譯的Go原始碼是對Grumpy執行時的一系列呼叫,Go庫提供與 Python C API類似的目的
如果是 Python3
系列,可以使用 PyPy
PythonNet
Jython3
ironpython3
等等
PyPy: https://bitbucket.org/pypy/pypy
Net方向:
https://github.com/pythonnet/pythonnet https://github.com/IronLanguages/ironpython3
Java方向:
https://github.com/jython/jython3
Other:
原始碼:https://github.com/sbinet/go-python 參考:https://studygolang.com/articles/13019 可惜CoreRT一直沒完善,不然就Happy了 https://github.com/dotnet/corert
經驗
: 平時基本上多執行緒就夠用了,如果想多核利用-多程序基本上就搞定了(分散式走起)實在不行一般都是分析一下效能瓶頸在哪,然後寫個擴充套件庫
如果需要和其他平臺互動才考慮上面說的這些專案。如果是Web專案就更不用擔心了,現在哪個公司還不是混用? JavaScript and Python and Go or Java or NetCore
。基本上上點規模的公司都會用到Python,之前都是 Python and Java
搭配使用,這幾年開始慢慢變成 Python and Go or NetCore
搭配使用了~
下集預估: Actor模型
and 訊息釋出/訂閱模型