標籤:append 線程 src 導致 線程鎖 tool oba threading 結果
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=011 12 l=[]13 for i in range(100):14 t=threading.Thread(target=sub,args=())15 t.start() #每一次線程啟用,申請一次gillock16 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=012 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+111 ‘‘‘線程的公用資料 上‘‘‘12 13 lock.release() #釋放鎖14 time.sleep(2)15 count=016 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 lock34 func foo ClockB lock35 func bar ClockB lock36 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,那麼計數器+126 #如果計數器大於0,那麼其他線程無法查收,如果碰到release,計數器-127 28 for i in range(10):29 t=threading.Thread(target=run,args=())30 t.start()
Semaphore(訊號量)
實際上也是一種鎖,該鎖用於限制線程的並發量
以下代碼在sleep兩秒後會列印出100個ok
1 import threading2 import time3 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()
python 多線程鎖機制