說說GIL

上一篇:線程深刻篇引入html

Code:https://github.com/lotapp/BaseCode/tree/master/python/5.concurrent/Thread/3.GILpython

說說GIL

儘管Python徹底支持多線程編程, 可是解釋器的C語言實現部分在徹底並行執行時並非線程安全的,因此這時候才引入了GILgit

解釋器被一個全局解釋器鎖保護着,它確保任什麼時候候都只有一個Python線程執行(保證C實現部分能線程安全) GIL最大的問題就是Python的多線程程序並不能利用多核CPU的優點 (好比一個使用了多個線程的計算密集型程序只會在一個單CPU上面運行)程序員

注意:GIL只會影響到那些嚴重依賴CPU的程序(好比計算型的)若是你的程序大部分只會涉及到I/O,好比網絡交互,那麼使用多線程就很合適 ~ 由於它們大部分時間都在等待(線程被限制到同一時刻只容許一個線程執行這樣一個執行模型。GIL會根據執行的字節碼行數和時間片來釋放GIL,在遇到IO操做的時候會主動釋放權限給其餘線程)github

因此Python的線程更適用於處理I/O和其餘須要併發執行的阻塞操做,而不是須要多處理器並行的計算密集型任務(對於IO操做來講,多進程和多線程性能差異不大)計算密集如今能夠用Python的Ray框架golang

網上摘取一段關於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()

運行時間:網絡

real    0m3.980s
user    0m2.034s
sys  0m3.119s

操做系統幾千個進程開銷仍是有點大的(畢竟進程是有上線的)ulimit -a
9.MaxProcess.png

測試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()

運行時間:

real    0m1.130s
user    0m0.158s
sys  0m0.095s

multiprocessing.dummy裏面的Process上面也說過了,就是在線程基礎上加點東西使得用起來和multiprocessingProcess編程風格基本一致(本質仍是線程)

測試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()

運行時間:

real    0m1.123s
user    0m0.154s
sys  0m0.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
9.共享庫.png

使用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
9.ctypes.png

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
9.golang.png


題外話~若是想等CPython的GIL消失能夠先看一個例子:MySQL把大鎖改爲各個小鎖花了5年。在是在MySQL有專門的團隊和公司前提下,而Python徹底靠社區重構就太慢了

速度方面微軟除外,更新快原本是好事,可是動不動斷層更新,這學習成本就太大了(這也是爲何Net能深刻的人比較少的緣由:人家剛深刻一個,你就淘汰一個了...)

可能還有人不清楚,貼下官方推薦技術吧(NetCoreOrleansEFCoreML.NetCoreRT

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兼容性是最好的了

若是是Python2系列我推薦谷歌的grumpy

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 消息發佈/訂閱模型

相關文章
相關標籤/搜索