互斥鎖: 為什麼要有互斥鎖:由於多線程是並行的,如果某一線程取出了某一個數據將要進行操作,但它還沒有那麼快執行完操作,這時候如果另外一個線程也要操作這個數據,那麼這個數據可能會因為兩次操作而發生錯誤 import time,threading x=6 def run1(): print("run1我... ...
互斥鎖:
- 為什麼要有互斥鎖:由於多線程是並行的,如果某一線程取出了某一個數據將要進行操作,但它還沒有那麼快執行完操作,這時候如果另外一個線程也要操作這個數據,那麼這個數據可能會因為兩次操作而發生錯誤
import time,threading x=6 def run1(): print("run1我拿到了數據:",x) print("我現在還不想操作,先睡一下") time.sleep(3) print("再看一下數據,穩一穩",x) def run2(): global x print("run2我拿到了數據:", x) x=5 print(x) t1=threading.Thread(target=run1) t2=threading.Thread(target=run2) t1.start() t2.start() t1.join() t2.join()
- 而多線程的互斥鎖機制本質上是:申請一個鎖,A線程拿了鑰匙【acquire】之後,如果B也想拿到鑰匙是不行的,只有等A把鑰匙還回來【release】才行
- 如何使用互斥鎖:
- 定義一個鎖對象:鎖對象=threading.Lock()
- 請求鎖:鎖對象.acquire()
- 釋放鎖:鎖對象.release()
使用互斥鎖來更改上段代碼
import time,threading x=6 def run1(): lock.acquire() global x print("run1我拿到了數據,x=",x) print("我現在還不想操作,先睡一下") time.sleep(3) print("再看一下數據,穩一穩,x=",x) x+=1 print("run1操作完畢:x=",x) lock.release() def run2(): lock.acquire() global x print("run2我拿到了數據:", x) x+=1 print("run2操作完畢:x=",x) lock.release() lock=threading.Lock()#生成一個鎖對象 t1=threading.Thread(target=run1) t2=threading.Thread(target=run2) t1.start() t2.start() start_time=time.time() t1.join() t2.join() print("最終的x=",x) print(time.time()-start_time)#3.0多說明,由於受到鎖的影響,run2要等待run1釋放lock,所以變成了串列
這種互斥鎖在操作系統中可以稱作“臨界區”,如果想瞭解更多:
https://baike.baidu.com/item/%E4%B8%B4%E7%95%8C%E5%8C%BA/8942134?fr=aladdin
遞歸鎖:
- 為什麼要有遞歸鎖:互斥鎖本質上是阻止其他線程進入,如果有兩個需要阻止其他線程進入的操作【像兩個人過獨木橋】,那麼需要兩個鎖,而想要鎖上第二個如果直接用第一個鎖的acquire會失敗,因為第一個鎖還沒release,我們可以選擇再定義一個互斥鎖對象來acquire,但這僅僅是兩層的情況下,如果多層的吧,那麼就需要定義好幾個互斥鎖對象了【而且由於對象變多,有時候會因為互相調用鎖而發生死鎖】。遞歸鎖就是為了處理這種情況,遞歸鎖對象允許多次acquire和多次release
- 發生死鎖的情況[A拿到A鎖,想要拿B鎖,B拿著B鎖,想要A鎖]
【以過獨木橋為例】:橋只能容一個人通過,A只能看得到北邊橋上有沒有人,看不到南邊橋有沒有人,當他看到北邊橋沒人就會過橋,等到他到橋中間才能看到南邊橋有沒有人,B情況相反:【於是當兩個人一起過橋的時候就會發生死鎖】
import threading,time """ A只能看得到北邊橋上有沒有人,看不到南邊橋有沒有人, 當他看到北邊橋沒人就會過橋,等到他到橋中間才能看到南邊橋有沒有人 """ def A(): lockNorth.acquire()#拿到北邊橋的鎖 print("A過橋北") time.sleep(3)#過橋中 lockSorth.acquire()#企圖過到南邊橋, print("A過橋南") time.sleep(3) # 過橋中 lockSorth.release() lockNorth.release() print("A過橋成功") """ B只能看得到南邊橋上有沒有人,看不到北邊橋有沒有人, 當他看到南邊橋沒人就會過橋,等到他到橋中間才能看到北邊橋有沒有人 """ def B(): lockSorth.acquire() # 企圖過到南邊橋, print("B過橋南") time.sleep(3) # 過橋中 lockNorth.acquire() # 拿到北邊橋的鎖 print("B過橋北") time.sleep(3) # 過橋中 lockNorth.release() lockSorth.release() print("B過橋成功") lockNorth=threading.Lock() lockSorth=threading.Lock() tA=threading.Thread(target=A) tB=threading.Thread(target=B) tA.start() tB.start() tA.join() tB.join()
- 遞歸鎖的本質是:本質上還是一個鎖,但如果在一個線程裡面可以多次acquire。【因為只有一個鎖,所以不會發生互相調用的死鎖,而因為可以多次調用,所以可以鎖多次】
- 如何使用遞歸鎖:
- 定義一個鎖對象:遞歸鎖對象=threading.RLock()
- 請求鎖:鎖對象.acquire()
- 釋放鎖:鎖對象.release()
使用遞歸鎖來解決上面的死鎖問題:
import threading,time """ A只能看得到北邊橋上有沒有人,看不到南邊橋有沒有人, 當他看到北邊橋沒人就會過橋,等到他到橋中間才能看到南邊橋有沒有人 """ def A(): lock.acquire()#拿到北邊橋的鎖 print("A過橋北") time.sleep(3)#過橋中 lock.acquire()#企圖過到南邊橋, print("A過橋南") time.sleep(3) # 過橋中 lock.release() lock.release() print("A過橋成功") """ B只能看得到南邊橋上有沒有人,看不到北邊橋有沒有人, 當他看到南邊橋沒人就會過橋,等到他到橋中間才能看到北邊橋有沒有人 """ def B(): lock.acquire() # 拿南橋鎖, print("B過橋南") time.sleep(3) # 過橋中 lock.acquire() # 企圖拿北橋的鎖 print("B過橋北") time.sleep(3) # 過橋中 lock.release() lock.release() print("B過橋成功") lock=threading.RLock() tA=threading.Thread(target=A) tB=threading.Thread(target=B) tA.start() tB.start() tA.join() tB.join()
【由於本質是一把鎖,A拿到鎖後,B要等待】
信號量:
- 什麼是信號量:
信號量可以限制進入的線程的數量。
- 如何使用信號量:
- 創建信號量對象:信號量對象=threading.BoundedSemaphore(x),x是限制進程的數量
- 當有進程需要進入的時候,調用acquire()來減少信號量:信號量對象.acquire()
- 當有進程離開的時候,調用release()來增加信號量:信號量對象.release()
import threading,time def run(): s.acquire() print("hello") time.sleep(1.5) s.release() s=threading.BoundedSemaphore(3)#限制3個 threading_list=[] for i in range(12):#創建12個線程 obj=threading.Thread(target=run) obj.setDaemon(True) # 設置守護線程,避免干擾主線程運行,並行等待 obj.start() for i in range(4): print("")#為了把結果分割,可以清楚看出分為了三組 time.sleep(1.5) #結果分為三組是因為運行的太快了,三個線程裝入的時間差太小
事件:
- 什麼是事件:當發生線程發生一件事的時候如果要提醒另外一個線程,使用事件。雙方共用該事件對象【等待的一方會阻塞而進行等待】,當一方更改事件對象的時候,另外一方也能知道【以讀者-寫者為例:讀者要等寫者告訴他去讀才會去讀,寫者寫完後要設置一個事件,當該事件設置時,讀者就會來讀】
- 如何使用事件:
- 創建事件對象:事件對象=threading.Event()
- 設置事件:事件對象.set() 判斷事件是否set:事件對象.is_set(),等待事件set:事件對象.wait()
- 清除事件:事件對象.clear()
import threading,time def read(): while True: if event.is_set(): print("事件已設置,我要讀了!!!!") time.sleep(1) else:#事件未設置 print("還沒寫好,我要等咯") event.wait()#那麼就等著咯 #如果等到了 print("終於等到了!那麼我又可以讀了") time.sleep(1) def write(): event.clear()#初始設空 while True: time.sleep(3)#寫 event.set()#設置事件,一旦set,那麼讀者wait就有返回了,讀者可以繼續運行了 print("write:寫好了") time.sleep(2)#等人讀 event.clear()#清除事件 event=threading.Event() #創建事件對象 t1=threading.Thread(target=write) t2=threading.Thread(target=read) t1.start() t2.start() t1.join() t2.join() """結果顯示:讀者確實一直在等待寫者寫好"""