「死鎖」 與 python多線程之threading模塊下的鎖機制

一:死鎖python

  在死鎖以前須要先了解的概念是「可搶佔資源」與「不可搶佔資源」【此處的資源能夠是硬件設備也能夠是一組信息】,由於死鎖是與不可搶佔資源有關的。數據庫

  可搶佔資源:能夠從擁有他的進程中搶佔而不會發生反作用。e.g:存儲器就是一類可搶佔資源(假設有A, B兩個進程都想用打印機對256MB的用戶內存進行打印,若A已經得到打印機而且開始打印,可是在沒有打印完成其時間片就用完並被換出了,此時B進程開始運行「搶佔了」內存並開始請求打印機,可是A進程還擁有打印機因此B進程沒有搶佔打印機成功,此時因爲雙方都缺乏一個對方擁有的資源,雙方都不能執行,有死鎖的危險,不過實際上系統會把進程B換出內存,從新將進程A換入內存 [也就是A進程反過來搶佔了B進程的內存資源],效果也就是進程A繼續執行直到完成並釋放打印機,然後B進程就可以執行了)。從中能夠看出因爲可搶佔資源能夠經過從新分配資源而避免了死鎖的發生,因此可搶佔資源是不會發生死鎖現象的。dom

  不可搶佔資源:在不引發相關計算失敗的狀況下,沒法把它從佔有它的進程處搶佔過來的資源。e.g:刻錄機(假設一個進程已經開始刻盤,忽然將刻錄機分配給另外一個進程,那麼將劃壞CD盤,因此任什麼時候候刻錄機都是不能被搶佔的)ide

  死鎖:當多個進程須要排他性的訪問多個資源,且其所須要的資源在同一時間屬於不一樣進程且仍是不可搶佔資源的時候,此時相關進程就會一直請求對應資源但得不到從而一直阻塞下去,這就是死鎖現象。函數

   *除了硬件機器與I/O設備以外,別的狀況也可能引發死鎖,好比一個數據庫系統中,爲了不競爭可對若干記錄加鎖,假設進程A對記錄R1加鎖而進程B歲記錄R2加鎖,接着他們又各自試着加鎖對方的記錄,此時也會發生死鎖ui

 

注:關於GIL(global interpreter lock),可參見這篇文章:http://www.dabeaz.com/python/UnderstandingGIL.pdfspa

二:Threading模塊操作系統

2.1.threading.Thread對象【建立線程的主要對象】:線程

  方法:start():啓動線程code

       run():啓動線程後自動調用的方法

     join([timeout]):等待到被調用的線程終止

       is_alive():返回線程活動狀態

  屬性:name:線程名

       ident:線程ID號

       daemon:後臺標誌

2.2.建立線程的方法:

  1:實例化threading.Thread對象

    ths = threading.Thread( target = None, name = None, args = (), kwarg = {} )

                  傳入函數名        線程名          函數的參數

  2:繼承threading中的Thread對象後再實例化並啓動

 

2.3.以不一樣的線程類型分:

  令:t = threading.Thread(target=dmn...),便是t爲一個線程

  2.3.1.獨立線程:就是最簡單的獨立單線程

  2.3.2.線程等待:t.join([timeout]),放到start()後,代表t線程執行完後(或是指定時間後)才輪到下一個線程【如果由於指定時間的話還會跳回t線程】

 1 #coding:utf-8
 2 import threading, time
 3 class MyThread(threading.Thread):
 4 
 5     def run(self):
 6         for i in range(40):
 7             print()    #如果t線程在執行時就是打印換行
 8             time.sleep(0.1)
 9 
10 if __name__ == "__main__":
11     t = MyThread()
12     t.start()
13     **t.join()**
14     for i in range(10):    #此部分是主線程執行的,也就是說此程序有主線程與t線程兩個線程
15         print("main:", i)
16         time.sleep(0.1)    
17 
18 #最後會在打印完了換行後纔打印0到9的「main:...」, 說明t線程執行完了後主線程才執行的
線程等待示例

  2.3.3.後臺線程(在主線程退出後當即自殺):設置daemon屬性爲True【 t.daemon = True】

  2.3.4.線程同步:指的是多個線程同時須要用到的資源,此時爲了保證一致性有多種不一樣的加鎖處理機制【獲取了鎖的線程能夠對此資源進行操做,而沒有的線程處於阻塞狀態】,以下:

    1)先到先用---指令鎖:threading.Lock對象

      定義鎖變量:lock = threading.Lock()

      鎖定資源:lock.acquire()

      釋放資源:lock.release()

 1 import threading, time, random
 2 share = 4
 3 class MyThread(threading.Thread):
 4     def __init__(self,i):
 5         super().__init__()
 6         self.i = i
 7 
 8     def run(self):
 9         global share
10         for d in range(40):
11             **lock.acquire()**
12             print(share)
13             share += self.i
14             time.sleep(random.random())
15             print("t+", self.i, "=", share)
16             **lock.release()**
17 
18 **lock = threading.Lock()**
19 
20 if __name__ == "__main__":
21     t = MyThread(2)
22     tt = MyThread(6)
23     t.start()
24     # t.join()
25     tt.start()
26 
27 #會看到一下子加2,一下子加6
28 #若將倒數第二行註釋去除,則一直加2,完了後才加6
指令鎖示例

    2)可重入鎖:被某一線程鎖定了後還能夠被其餘線程鎖定

    3)有前後順序的使用---條件變量:threading.Condition對象(建立了一個帶鎖的線程池), [當具有了某條件後才調用某線程,如生產者-消費者模式]

      定義鎖變量:conLock  = threading.Condition()

      得到鎖:conLock.acquire()

      通知線程池裏其餘線程:conLock.notify( [ n=x ] )【喚起x個,默認爲1】,clock.notify_all()【喚醒所有】

      釋放鎖而且進入線程池等待:conLock.wait()

 1 #coding:utf-8
 2 import threading, time
 3 share = 0
 4 **share_condition = threading.Condition()**
 5 
 6 class ProductThread(threading.Thread):
 7     def __init__(self):
 8         super().__init__()
 9         self.name = "生產者"
10 
11     def run(self):
12         global share
13         if share_condition.acquire():
14             while True:
15                 if not share:
16                     share += 1
17                     print( self.name, share )
18                     **share_condition.notify()**       #執行線程池裏的一個等待線程
19                 **share_condition.wait()**             #當前線程釋放鎖並將當前線程放入線程池等待下次notify(),因此若是沒有此句則只會顯示一句「生產者:1」
20                 time.sleep(1)
21 
22 class CustumerThread(threading.Thread):
23     def __init__(self):
24         super().__init__()
25         self.name = "消費者"
26 
27     def run(self):
28         global share
29         if share_condition.acquire():
30             while True:
31                 if share:
32                     share -= 1
33                     print( self.name, share )
34                     **share_condition.notify()**
35                 **share_condition.wait()**
36                 time.sleep(1)
37 
38 lock = threading.Lock()
39 
40 if __name__ == "__main__":
41     product = ProductThread()
42     customer = CustumerThread()
43     product.start()
44     customer.start()
45 
46 
47 #最後會相繼打印:
48 # 生產者 1
49 # 消費者 0
條件變量鎖機制示例

    4)部分線程同時使用---信號量:threading.Semaphore對象

      定義鎖:sema = threading.Semaphore(n)  #容許同時有n個線程得到鎖

      得到鎖:sema.acquire()

      釋放鎖:sema.release()

      經常使用於限制資源的訪問,好比數據庫的鏈接池

 1 #coding:utf-8
 2 import threading, time
 3 class MyThread(threading.Thread):
 4     def __init__(self,name):
 5         super().__init__()
 6         self.name = name
 7 
 8     def run(self):
 9         if sema.acquire():
10             print(self.name,"獲得了鎖")
11             time.sleep(1)
12         sema.release()
13         print(self.name,'釋放了鎖')
14 
15 sema = threading.Semaphore(2)
16 
17 if __name__ == "__main__":
18     threads = [ MyThread(str(i)+"sema") for i in range(5) ]
19     for thread in threads:
20         thread.start()
21 
22 #輸出以下,能夠看出同時可以有兩個線程得到了鎖
23 # 0sema 獲得了鎖
24 # 1sema 獲得了鎖
25 # 0sema 釋放了鎖
26 # 1sema 釋放了鎖
27 # 2sema 獲得了鎖
28 # 3sema 獲得了鎖
29 # 2sema 釋放了鎖
30 # 4sema 獲得了鎖
31 # 3sema 釋放了鎖
32 # 4sema 釋放了鎖
信號量鎖示例

    5)線程間通訊---threading.Event對象(建立了一個帶有標誌的線程池,當一個線程設置內部標誌爲True後線程池中的等待線程就會被喚醒並執行)【便是當前執行線程能夠決定啥時候(通常是所共同使用的資源已經使用完畢了後)「叫醒」別的線程,須要注意的是,若此時叫醒別人的線程並無wait進入線程池,那麼若是其還有代碼的話將繼續執行下去】

      定義鎖變量: event = threading.Event()

      進入線程池等待:event.wait([timeout])

      設置內部標誌爲True:event.set()  <--->  event.clear()

 1 #coding:utf-8
 2 import threading, time
 3 
 4 class WaitThread(threading.Thread):
 5 
 6     def run(self):
 7         self.name = "等待線程"
 8         print(self.name,"正在等待") #注意此程序的執行順序,先調用的此線程執行到這裏
 9         **event.wait()**               #在此此線程wait進入線程池等待,切換到mainThread直至設置標誌
10         print(self.name,"啓動了")  #從而此線程在此被喚醒執行
11         **event.clear()**              #然後設置標誌爲false
12 
13 class MainThread(threading.Thread):
14 
15     def run(self):
16         time.sleep(3)
17         print("主線程更改了標誌")
18         **event.set()**
19         print("這裏的會在WaitThread打印啓動了前打印嗎?") 
20 
21 **event = threading.Event()**
22 
23 if __name__ == "__main__":
24     wt = WaitThread()
25     mt = MainThread()
26     wt.start()
27     mt.start()
28 
29 # 輸出:
30 # 等待線程 正在等待
31 # 主線程更改了標誌
32 # 這裏的會在WaitThread打印啓動了前打印嗎?
33 # 等待線程 啓動了
線程間通訊鎖示例

    

*補充.關於定時執行:threading.Timer()

 

參考:

《現代操做系統》---Andrew S. Tanebaum

相關文章
相關標籤/搜索