簡單來說,死鎖是一個資源被多次呼叫,而多次呼叫方都未能釋放該資源就會造成死鎖,這裡結合例子說明下兩種常見的死鎖情況。
1、迭代死鎖
該情況是一個執行緒“迭代”請求同一個資源,直接就會造成死鎖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import threading import time class MyThread(threading.Thread): def run(self): global num time.sleep(1) if mutex.acquire(1): num = num+1 msg = self.name+' set num to '+str(num) print msg mutex.acquire() mutex.release() mutex.release() num = 0 mutex = threading.Lock() def test(): for i in range(5): t = MyThread() t.start() if __name__ == '__main__': test() |
上例中,在run函式的if判斷中第一次請求資源,請求後還未 release ,再次acquire,最終無法釋放,造成死鎖。這裡例子中通過將print下面的兩行註釋掉就可以正常執行了 ,除此之外也可以通過可重入鎖解決,後面會提到。
2、互相呼叫死鎖
上例中的死鎖是在同一個def函式內多次呼叫造成的,另一種情況是兩個函式中都會呼叫相同的資源,互相等待對方結束的情況。如果兩個執行緒分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import threading import time class MyThread(threading.Thread): def do1(self): global resA, resB if mutexA.acquire(): msg = self.name+' got resA' print msg if mutexB.acquire(1): msg = self.name+' got resB' print msg mutexB.release() mutexA.release() def do2(self): global resA, resB if mutexB.acquire(): msg = self.name+' got resB' print msg if mutexA.acquire(1): msg = self.name+' got resA' print msg mutexA.release() mutexB.release() def run(self): self.do1() self.do2() resA = 0 resB = 0 mutexA = threading.Lock() mutexB = threading.Lock() def test(): for i in range(5): t = MyThread() t.start() if __name__ == '__main__': test() |
這個死鎖的示例稍微有點複雜。具體可以理下。
二、可重入鎖
為了支援在同一執行緒中多次請求同一資源,python提供了“可重入鎖”:threading.RLock。RLock內部維護著一個Lock和一個counter變數,counter記錄了acquire的次數,從而使得資源可以被多次require。直到一個執行緒所有的acquire都被release,其他的執行緒才能獲得資源。這裡以例1為例,如果使用RLock代替Lock,則不會發生死鎖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import threading import time class MyThread(threading.Thread): def run(self): global num time.sleep(1) if mutex.acquire(1): num = num+1 msg = self.name+' set num to '+str(num) print msg mutex.acquire() mutex.release() mutex.release() num = 0 mutex = threading.RLock() def test(): for i in range(5): t = MyThread() t.start() if __name__ == '__main__': test() |
和上面那個例子的不同之處在於threading.Lock()換成了threading.RLock() 。
三、互斥鎖
python threading模組有兩類鎖:互斥鎖(threading.Lock )和可重用鎖(threading.RLock)。兩者的用法基本相同,具體如下:
1 2 3 4 |
lock = threading.Lock() lock.acquire() dosomething…… lock.release() |
RLock的用法是將threading.Lock()修改為threading.RLock()。便於理解,先來段程式碼:
1 |
[root@361way lock]# cat lock1.py |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/usr/bin/env python # coding=utf-8 import threading # 匯入threading模組 import time # 匯入time模組 class mythread(threading.Thread): # 通過繼承建立類 def __init__(self,threadname): # 初始化方法 # 呼叫父類的初始化方法 threading.Thread.__init__(self,name = threadname) def run(self): # 過載run方法 global x # 使用global表明x為全域性變數 for i in range(3): x = x + 1 time.sleep(5) # 呼叫sleep函式,讓執行緒休眠5秒 print x tl = [] # 定義列表 for i in range(10): t = mythread(str(i)) # 類例項化 tl.append(t) # 將類物件新增到列表中 x=0 # 將x賦值為0 for i in tl: i.start() |
這裡執行的結果和想想的不同,結果如下:
1 |
[root@361way lock]# python lock1.py |
1 2 3 4 5 6 7 8 9 10 |
30 30 30 30 30 30 30 30 30 30 |
為什麼結果都是30呢?關鍵在於global 行和 time.sleep行。
1、由於x是一個全域性變數,所以每次迴圈後 x 的值都是執行後的結果值;
2、由於該程式碼是多執行緒的操作,所以在sleep 等待的時候,之前已經執行完成的執行緒會在這等待,而後續的程式在等待的5秒這段時間也執行完成 ,等待print。同樣由於global 的原理,x被重新斌值。所以列印出的結果全是30 ;
3、便於理解,可以嘗試將sleep等註釋,你再看下結果,就會發現有不同。
在實際應用中,如抓取程式等,也會出現類似於sleep等待的情況。在前後呼叫有順序或列印有輸出的時候,就會現併發競爭,造成結果或輸出紊亂。這裡就引入了鎖的概念,上面的程式碼修改下,如下:
1 |
[root@361way lock]# cat lock2.py |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#!/usr/bin/env python # coding=utf-8 import threading # 匯入threading模組 import time # 匯入time模組 class mythread(threading.Thread): # 通過繼承建立類 def __init__(self,threadname): # 初始化方法 threading.Thread.__init__(self,name = threadname) def run(self): # 過載run方法 global x # 使用global表明x為全域性變數 lock.acquire() # 呼叫lock的acquire方法 for i in range(3): x = x + 1 time.sleep(5) # 呼叫sleep函式,讓執行緒休眠5秒 print x lock.release() # 呼叫lock的release方法 lock = threading.Lock() # 類例項化 tl = [] # 定義列表 for i in range(10): t = mythread(str(i)) # 類例項化 tl.append(t) # 將類物件新增到列表中 x=0 # 將x賦值為0 for i in tl: i.start() # 依次執行執行緒 |
執行的結果如下:
1 |
[root@361way lock]# python lock2.py |
1 2 3 4 5 6 7 8 9 10 |
3 6 9 12 15 18 21 24 27 30 |
加鎖的結果會造成阻塞,而且會造成開鎖大。會根據順序由併發的多執行緒按順序輸出,如果後面的執行緒執行過快,需要等待前面的程式結束後其才能結束 --- 寫的貌似有點像佇列的概念了 ,不過在加鎖的很多場景下確實可以通過佇列去解決。