GIL(全局解釋器鎖)
GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念,是為了實現不同線程對共享資源訪問的互斥,才引入了GIL
在Cpython解釋器中,同一個進程下開啟的多線程,同一時刻只能有一個線程執行,無法利用多核優勢
python對於計算密集型的任務開多線程的效率甚至不如串行(沒有大量切換),但是,對於IO密集型的任務效率還是有顯著提升的。
GIL原理圖
計算密集型:結果肯定是100,因為每一次start結果就已經出來了,所以第二個線程肯定是通過調用第一個線程的count值進行計算的
1 def sub(): 2 global count 3 4 '''線程的公共數據 下''' 5 temp=count 6 count=temp+1 7 '''線程的公共數據 上''' 8 9 time.sleep(2) 10 count=0 11 12 l=[] 13 for i in range(100): 14 t=threading.Thread(target=sub,args=()) 15 t.start() #每一次線程激活,申請一次gillock 16 l.append(t) 17 for t in l: 18 t.join() 19 print(count)
io密集型:當第一個線程開始start的時候,由於sleep了0.001秒,這0.001秒對於人而言很短,但是對於cpu而言,這0.001秒已經做了很多的事情了,在這里cpu做的事情就是或許已經start了100個線程,所以導致大多數的線程調用的count值還是0,即temp=0,只有少數的線程完成了count=temp+1的操作,所以輸出的count結果不確定,可能是7、8、9,也可能是10幾。
1 def sub(): 2 global count 3 4 '''線程的公共數據 下''' 5 temp=count 6 time.sleep(0.001) #大量的io操作 7 count=temp+1 8 '''線程的公共數據 上''' 9 10 time.sleep(2) 11 count=0 12 13 l=[] 14 for i in range(100): 15 t=threading.Thread(target=sub,args=()) 16 t.start() 17 l.append(t) 18 for t in l: 19 t.join() 20 print(count)
注意以下的鎖都是多線程提供的鎖機制,與python解釋器引入的gil概念無關
互斥鎖(同步鎖)
互斥鎖是用來解決上述的io密集型場景產生的計算錯誤,即目的是為了保護共享的數據,同一時間只能有一個線程來修改共享的數據。
1 def sub(): 2 global count 3 lock.acquire() #上鎖,第一個線程如果申請到鎖,會在執行公共數據的過程中持續阻塞后續線程 4 #即后續第二個或其他線程依次來了發現已經被上鎖,只能等待第一個線程釋放鎖 5 #當第一個線程將鎖釋放,后續的線程會進行爭搶 6 7 '''線程的公共數據 下''' 8 temp=count 9 time.sleep(0.001) 10 count=temp+1 11 '''線程的公共數據 上''' 12 13 lock.release() #釋放鎖 14 time.sleep(2) 15 count=0 16 17 l=[] 18 lock=threading.Lock() #將鎖內的代碼串行化 19 for i in range(100): 20 t=threading.Thread(target=sub,args=()) 21 t.start() 22 l.append(t) 23 for t in l: 24 t.join() 25 print(count)
死鎖
保護不同的數據就應該加不同的鎖。
所以當有多個互斥鎖存在的時候,可能會導致死鎖,死鎖原理如下:
1 import threading 2 import time 3 def foo(): 4 lockA.acquire() 5 print('func foo ClockA lock') 6 lockB.acquire() 7 print('func foo ClockB lock') 8 lockB.release() 9 lockA.release() 10 11 def bar(): 12 13 lockB.acquire() 14 print('func bar ClockB lock') 15 time.sleep(2) # 模擬io或者其他操作,第一個線程執行到這,在這個時候,lockA會被第二個進程占用 16 # 所以第一個進程無法進行后續操作,只能等待lockA鎖的釋放 17 lockA.acquire() 18 print('func bar ClockA lock') 19 lockB.release() 20 lockA.release() 21 22 def run(): 23 foo() 24 bar() 25 26 lockA=threading.Lock() 27 lockB=threading.Lock() 28 for i in range(10): 29 t=threading.Thread(target=run,args=()) 30 t.start() 31 32 輸出結果:只有四行,因為產生了死鎖阻斷了 33 func foo ClockA lock 34 func foo ClockB lock 35 func bar ClockB lock 36 func foo ClockA lock
遞歸鎖(重要)
解決死鎖
1 import threading 2 import time 3 def foo(): 4 rlock.acquire() 5 print('func foo ClockA lock') 6 rlock.acquire() 7 print('func foo ClockB lock') 8 rlock.release() 9 rlock.release() 10 11 def bar(): 12 rlock.acquire() 13 print('func bar ClockB lock') 14 time.sleep(2) 15 rlock.acquire() 16 print('func bar ClockA lock') 17 rlock.release() 18 rlock.release() 19 20 21 def run(): 22 foo() 23 bar() 24 25 rlock=threading.RLock() #RLock本身有一個計數器,如果碰到acquire,那么計數器+1 26 #如果計數器大於0,那么其他線程無法查收,如果碰到release,計數器-1 27 28 for i in range(10): 29 t=threading.Thread(target=run,args=()) 30 t.start()
Semaphore(信號量)
實際上也是一種鎖,該鎖用於限制線程的並發量
以下代碼在sleep兩秒后會打印出100個ok
1 import threading 2 import time 3 def foo(): 4 time.sleep(2) 5 print('ok') 6 7 for i in range(100): 8 t=threading.Thread(target=foo,args=()) 9 t.start()
每2秒打印5次ok
1 import threading 2 import time 3 sem=threading.Semaphore(5) 4 def foo(): 5 sem.acquire() 6 time.sleep(2) 7 print('ok') 8 sem.release() 9 10 for i in range(100): 11 t=threading.Thread(target=foo,args=()) 12 t.start()