網(wǎng)站首頁 編程語言 正文
本節(jié)重點
- 掌握Cpython的GIL解釋器鎖的工作機制
- 掌握GIL與互斥鎖
- 掌握Cpython下多線程與多進程各自的應用場景
本節(jié)時長需控制在45分鐘內(nèi)
一 引子
定義:
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple?
native threads from executing Python bytecodes at once. This lock is necessary mainly?
because CPython’s memory management is not thread-safe. (However, since the GIL?
exists, other features have grown to depend on the guarantees that it enforces.)
結(jié)論:在Cpython解釋器中,同一個進程下開啟的多線程,同一時刻只能有一個線程執(zhí)行,無法利用多核優(yōu)勢
首先需要明確的一點是GIL并不是Python的特性,它是在實現(xiàn)Python解析器(CPython)時所引入的一個概念。就好比C++是一套語言(語法)標準,但是可以用不同的編譯器來編譯成可執(zhí)行代碼。
有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也一樣,同樣一段代碼可以通過CPython,PyPy,Psyco等不同的Python執(zhí)行環(huán)境來執(zhí)行。
像其中的JPython就沒有GIL。然而因為CPython是大部分環(huán)境下默認的Python執(zhí)行環(huán)境。
所以在很多人的概念里CPython就是Python,也就想當然的把GIL歸結(jié)為Python語言的缺陷。
所以這里要先明確一點:GIL并不是Python的特性,Python完全可以不依賴于GIL
二 GIL介紹
GIL本質(zhì)就是一把互斥鎖,既然是互斥鎖,所有互斥鎖的本質(zhì)都一樣,都是將并發(fā)運行變成串行,以此來控制同一時間內(nèi)共享數(shù)據(jù)只能被一個任務所修改,進而保證數(shù)據(jù)安全。
可以肯定的一點是:保護不同的數(shù)據(jù)的安全,就應該加不同的鎖。
要想了解GIL,首先確定一點:每次執(zhí)行python程序,都會產(chǎn)生一個獨立的進程。例如python test.py,python aaa.py,python bbb.py會產(chǎn)生3個不同的python進程
驗證python test.py只會產(chǎn)生一個進程
#test.py內(nèi)容
import os,time
print(os.getpid())
time.sleep(1000)
#打開終端執(zhí)行
python3 test.py
#在windows下查看
tasklist |findstr python
#在linux下下查看
ps aux |grep python
在一個python的進程內(nèi),不僅有test.py的主線程或者由該主線程開啟的其他線程,還有解釋器開啟的垃圾回收等解釋器級別的線程,總之,所有線程都運行在這一個進程內(nèi),毫無疑問
1、所有數(shù)據(jù)都是共享的,這其中,代碼作為一種數(shù)據(jù)也是被所有線程共享的(test.py的所有代碼以及Cpython解釋器的所有代碼)
例如:test.py定義一個函數(shù)work(代碼內(nèi)容如下圖),在進程內(nèi)所有線程都能訪問到work的代碼,于是我們可以開啟三個線程然后target都指向該代碼,能訪問到意味著就是可以執(zhí)行。
2、所有線程的任務,都需要將任務的代碼當做參數(shù)傳給解釋器的代碼去執(zhí)行,即所有的線程要想運行自己的任務,首先需要解決的是能夠訪問到解釋器的代碼。
綜上:
如果多個線程的target=work,那么執(zhí)行流程是
多個線程先訪問到解釋器的代碼,即拿到執(zhí)行權(quán)限,然后將target的代碼交給解釋器的代碼去執(zhí)行
解釋器的代碼是所有線程共享的,所以垃圾回收線程也可能訪問到解釋器的代碼而去執(zhí)行,這就導致了一個問題:對于同一個數(shù)據(jù)100,可能線程1執(zhí)行x=100的同時,而垃圾回收執(zhí)行的是回收100的操作,解決這種問題沒有什么高明的方法,就是加鎖處理,如下圖的GIL,保證python解釋器同一時間只能執(zhí)行一個任務的代碼
三 GIL與Lock
機智的同學可能會問到這個問題:Python已經(jīng)有一個GIL來保證同一時間只能有一個線程來執(zhí)行了,為什么這里還需要lock?
首先,我們需要達成共識:鎖的目的是為了保護共享的數(shù)據(jù),同一時間只能有一個線程來修改共享的數(shù)據(jù)
然后,我們可以得出結(jié)論:保護不同的數(shù)據(jù)就應該加不同的鎖。
最后,問題就很明朗了,GIL 與Lock是兩把鎖,保護的數(shù)據(jù)不一樣,前者是解釋器級別的(當然保護的就是解釋器級別的數(shù)據(jù),比如垃圾回收的數(shù)據(jù)),后者是保護用戶自己開發(fā)的應用程序的數(shù)據(jù),很明顯GIL不負責這件事,只能用戶自定義加鎖處理,即Lock,如下圖
分析:
1、100個線程去搶GIL鎖,即搶執(zhí)行權(quán)限
2、肯定有一個線程先搶到GIL(暫且稱為線程1),然后開始執(zhí)行,一旦執(zhí)行就會拿到lock.acquire()
3、極有可能線程1還未運行完畢,就有另外一個線程2搶到GIL,然后開始運行,但線程2發(fā)現(xiàn)互斥鎖lock還未被線程1釋放,于是阻塞,被迫交出執(zhí)行權(quán)限,即釋放GIL
4、直到線程1重新?lián)尩紾IL,開始從上次暫停的位置繼續(xù)執(zhí)行,直到正常釋放互斥鎖lock,然后其他的線程再重復2 3 4的過程
代碼示范
from threading import Thread,Lock
import os,time
def work():
global n
lock.acquire()
temp=n
time.sleep(0.1)
n=temp-1
lock.release()
if __name__ == '__main__':
lock=Lock()
n=100
l=[]
for i in range(100):
p=Thread(target=work)
l.append(p)
p.start()
for p in l:
p.join()
print(n) #結(jié)果肯定為0,由原來的并發(fā)執(zhí)行變成串行,犧牲了執(zhí)行效率保證了數(shù)據(jù)安全,不加鎖則結(jié)果可能為99
四 GIL與多線程
有了GIL的存在,同一時刻同一進程中只有一個線程被執(zhí)行
聽到這里,有的同學立馬質(zhì)問:進程可以利用多核,但是開銷大,而python的多線程開銷小,但卻無法利用多核優(yōu)勢,也就是說python沒用了,php才是最牛逼的語言?
別著急啊,老娘還沒講完呢。
要解決這個問題,我們需要在幾個點上達成一致:
1、cpu到底是用來做計算的,還是用來做I/O的?
2、多cpu,意味著可以有多個核并行完成計算,所以多核提升的是計算性能
3、每個cpu一旦遇到I/O阻塞,仍然需要等待,所以多核對I/O操作沒什么用處
一個工人相當于cpu,此時計算相當于工人在干活,I/O阻塞相當于為工人干活提供所需原材料的過程,工人干活的過程中如果沒有原材料了,則工人干活的過程需要停止,直到等待原材料的到來。
如果你的工廠干的大多數(shù)任務都要有準備原材料的過程(I/O密集型),那么你有再多的工人,意義也不大,還不如一個人,在等材料的過程中讓工人去干別的活,
反過來講,如果你的工廠原材料都齊全,那當然是工人越多,效率越高
結(jié)論:
1、對計算來說,cpu越多越好,但是對于I/O來說,再多的cpu也沒用
2、當然對運行一個程序來說,隨著cpu的增多執(zhí)行效率肯定會有所提高(不管提高幅度多大,總會有所提高),這是因為一個程序基本上不會是純計算或者純I/O,所以我們只能相對的去看一個程序到底是計算密集型還是I/O密集型,從而進一步分析python的多線程到底有無用武之地
假設(shè)我們有四個任務需要處理,處理方式肯定是要玩出并發(fā)的效果,解決方案可以是:
方案一:開啟四個進程
方案二:一個進程下,開啟四個線程
- 單核情況下,分析結(jié)果:
如果四個任務是計算密集型,沒有多核來并行計算,方案一徒增了創(chuàng)建進程的開銷,方案二勝
如果四個任務是I/O密集型,方案一創(chuàng)建進程的開銷大,且進程的切換速度遠不如線程,方案二勝
- 多核情況下,分析結(jié)果:
如果四個任務是計算密集型,多核意味著并行計算,在python中一個進程中同一時刻只有一個線程執(zhí)行用不上多核,方案一勝
如果四個任務是I/O密集型,再多的核也解決不了I/O問題,方案二勝
結(jié)論:
現(xiàn)在的計算機基本上都是多核,python對于計算密集型的任務開多線程的效率并不能帶來多大性能上的提升,甚至不如串行(沒有大量切換),但是,對于IO密集型的任務效率還是有顯著提升的。
五 多線程性能測試
如果并發(fā)的多個任務是計算密集型:多進程效率高
from multiprocessing import Process
from threading import Thread
import os,time
def work():
res=0
for i in range(100000000):
res*=i
if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本機為4核
start=time.time()
for i in range(4):
p=Process(target=work) #耗時5s多
p=Thread(target=work) #耗時18s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
如果并發(fā)的多個任務是I/O密集型:多線程效率高
from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
time.sleep(2)
print('===>')
if __name__ == '__main__':
l=[]
print(os.cpu_count()) #本機為4核
start=time.time()
for i in range(400):
# p=Process(target=work) #耗時12s多,大部分時間耗費在創(chuàng)建進程上
p=Thread(target=work) #耗時2s多
l.append(p)
p.start()
for p in l:
p.join()
stop=time.time()
print('run time is %s' %(stop-start))
應用:
多線程用于IO密集型,如socket,爬蟲,web
多進程用于計算密集型,如金融分析
原文鏈接:https://www.jianshu.com/p/1f2498f05b6c
相關(guān)推薦
- 2022-07-09 kernel劫持modprobe?path內(nèi)容詳解_C 語言
- 2022-09-04 Python基礎(chǔ)之字典的詳細使用教程_python
- 2023-03-18 C++虛函數(shù)和多態(tài)超詳細分析_C 語言
- 2022-07-24 Android中關(guān)于定時任務實現(xiàn)關(guān)閉訂單問題_Android
- 2022-04-09 一起來了解python的if流程控制_python
- 2022-08-23 使用Python腳本提取基因組指定位置序列_python
- 2023-01-18 高斯衰減python實現(xiàn)方式_python
- 2022-07-13 查看nginx連接數(shù)
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支