python互斥鎖和死鎖

同步的概念

同步就是協同步調,按預定的先後次序進行運行。如:你說完,我再說。

"同"字從字面上容易理解爲一起動作

其實不是,"同"字應是指協同、協助、互相配合。

如進程、線程同步,可理解爲進程或線程A和B一塊配合,A執行到一定程度時要依靠B的某個結果,於是停下來,示意B運行;B執行,再將結果給A;A再繼續操作。

解決線程同時修改全局變量的方式
對於上一小節提出的那個計算錯誤的問題,可以通過線程同步來進行解決

思路,如下:
系統調用t1,然後獲取到g_num的值爲0,此時上一把鎖,即不允許其他線程操作g_num
t1對g_num的值進行+1
t1解鎖,此時g_num的值爲1,其他的線程就可以使用g_num了,而且是g_num的值不是0而是1
同理其他線程在對g_num進行修改時,都要先上鎖,處理完後再解鎖,在上鎖的整個過程中不允許其他線程訪問,就保證了數據的正確性

互斥鎖

當多個線程幾乎同時修改某一個共享數據的時候,需要進行同步控制

線程同步能夠保證多個線程安全訪問競爭資源,最簡單的同步機制是引入互斥鎖。

互斥鎖爲資源引入一個狀態:鎖定/非鎖定

某個線程要更改共享數據時,先將其鎖定,此時資源的狀態爲“鎖定”,其他線程不能更改;直到該線程釋放資源,將資源的狀態變成“非鎖定”,其他的線程才能再次鎖定該資源。互斥鎖保證了每次只有一個線程進行寫入操作,從而保證了多線程情況下數據的正確性。
threading模塊中定義了Lock類,可以方便的處理鎖定:

lock = threading.Lock() #創建鎖

lock.acquire() #上鎖

lock.release() #解鎖

如果這個鎖之前是沒有上鎖的,那麼acquire不會堵塞
如果在調用acquire對這個鎖上鎖之前 它已經被 其他線程上了鎖,那麼此時acquire會堵塞,直到這個鎖被解鎖爲止

使用互斥鎖自加:

from threading import Thread
from threading import Lock
import time
g_num = 0
# 創建一個全局鎖對象
glock = Lock()
def work1(num):
    global g_num
    glock.acquire()# 給該線程加鎖
    for i in range(num):
        g_num+=1
    glock.release()#解鎖
    print("in work1-->",g_num)

def work2(num):
    global g_num
    glock.acquire()
    for i in range(num):
        g_num+=1
    glock.release()
    print("in work2-->",g_num)

def main():
    t1 = Thread(target=work1,args=(1000000,))
    t2 = Thread(target=work2,args=(1000000,))
    t1.start()
    t2.start()
    t2.join()


if __name__ == '__main__':
    main()
    print("main in-->",g_num)

運行結果:

in work1--> 1000000
in work2--> 2000000
main in--> 2000000

死鎖

在線程間共享多個資源的時候,如果兩個線程分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖。
儘管死鎖很少發生,但一旦發生就會造成應用的停止響應。下面看一個死鎖的例子
簡單來說就是:兩個線程佔用一個資源,互不相讓,導致程序堵塞

from threading import Thread
from threading import Lock
import time
lock1 = Lock()# 創建第一個鎖
lock2 = Lock()# 創建第二個鎖
def work1(num):
   lock1.acquire() #lock1上鎖
   time.sleep(1) # 等待一秒鐘
   print("in work1")
   lock2.acquire()# lock2上鎖
   print("work1----->")
   lock2.release()# 解鎖lock2
   lock1.release() # 解鎖lock1

def work2(num):
    lock2.acquire() # 給lock2上鎖
    print("in work2")
    lock1.acquire()  # 給lock1上鎖
    print("work2---->")
    lock1.release() # 進行解鎖
    lock2.release()

if __name__ == '__main__':
    t1 = Thread(target=work1,args=(100000,)) # 創建線程t1
    t2 = Thread(target=work2,args=(100000,))# 創建線程t2
    t1.start() # 啓動線程
    t2.start()

運行結果:
在這裏插入圖片描述

避免死鎖
程序設計時要儘量避免(銀行家算法)
添加超時時間等
附錄-銀行家算法
[背景知識]
一個銀行家如何將一定數目的資金安全地借給若干個客戶,使這些客戶既能借到錢完成要乾的事,同時銀行家又能收回全部資金而不至於破產,這就是銀行家問題。這個問題同操作系統中資源分配問題十分相似:銀行家就像一個操作系統,客戶就像運行的進程,銀行家的資金就是系統的資源。

[問題的描述]

一個銀行家擁有一定數量的資金,有若干個客戶要貸款。每個客戶須在一開始就聲明他所需貸款的總額。若該客戶貸款總額不超過銀行家的資金總數,銀行家可以接收客戶的要求。客戶貸款是以每次一個資金單位(如1萬RMB等)的方式進行的,客戶在借滿所需的全部單位款額之前可能會等待,但銀行家須保證這種等待是有限的,可完成的。

例如:有三個客戶C1,C2,C3,向銀行家借款,該銀行家的資金總額爲10個資金單位,其中C1客戶要借9各資金單位,C2客戶要借3個資金單位,C3客戶要借8個資金單位,總計20個資金單位。某一時刻的狀態如圖所示。

在這裏插入圖片描述
對於a圖的狀態,按照安全序列的要求,我們選的第一個客戶應滿足該客戶所需的貸款小於等於銀行家當前所剩餘的錢款,可以看出只有C2客戶能被滿足:C2客戶需1個資金單位,小銀行家手中的2個資金單位,於是銀行家把1個資金單位借給C2客戶,使之完成工作並歸還所借的3個資金單位的錢,進入b圖。同理,銀行家把4個資金單位借給C3客戶,使其完成工作,在c圖中,只剩一個客戶C1,它需7個資金單位,這時銀行家有8個資金單位,所以C1也能順利借到錢並完成工作。最後(見圖d)銀行家收回全部10個資金單位,保證不賠本。那麼客戶序列{C1,C2,C3}就是個安全序列,按照這個序列貸款,銀行家纔是安全的。否則的話,若在圖b狀態時,銀行家把手中的4個資金單位借給了C1,則出現不安全狀態:這時C1,C3均不能完成工作,而銀行家手中又沒有錢了,系統陷入僵持局面,銀行家也不能收回投資。

綜上所述,銀行家算法是從當前狀態出發,逐個按安全序列檢查各客戶誰能完成其工作,然後假定其完成工作且歸還全部貸款,再進而檢查下一個能完成工作的客戶,…。如果所有客戶都能完成工作,則找到一個安全序列,銀行家纔是安全的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章