python線程互斥鎖和死鎖問題詳解與演示

  1.什麼是互斥鎖    

       當多個線程幾乎同時修改某一個共享數據的時候,需要進行同步控制。線程同步能夠保證多個線程安全訪問競爭資源,最簡單的同步,是引入互斥鎖。互斥鎖爲資源引入一個狀態:鎖定/非鎖定

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

2.互斥鎖的實現與原理

 同樣上面一個多線程共享全局變量的風險問題,這裏引入互斥鎖解決了這個問題

import threading
import time

g_num = 0

# 創建一個互斥鎖,全局變量,多線程共享
# 默認是未上鎖的狀態
mylock =  threading.Lock()

def work1(num):
    global g_num
    for i in range(num):
        mylock.acquire()  # 上鎖,可以測試下將鎖上在循環外。
        g_num += 1
        mylock.release()  # 釋放鎖
    print("----in work1, g_num is %d---"%g_num)

def work2(num):
    global g_num
    for i in range(num):
        mylock.acquire() #上鎖
        g_num += 1
        mylock.release() #釋放鎖

    print("----in work2, g_num is %d---"%g_num)

print("---線程創建之前g_num is %d---"%g_num)

t1 = threading.Thread(target=work1, args=(1000000,))  #給線程執行的函數傳參,注意這裏args只是變量名,可以自己定義。
t1.start()

#time.sleep(5)  #使用延時,讓work1執行完,再執行work2也可以,效率太低。

t2 = threading.Thread(target=work2, args=(1000000,))
t2.start()

while len(threading.enumerate()) != 1:
    time.sleep(1)

print("2個線程對同一個全局變量操作之後的最終結果是:%s" % g_num)

要點總結:

1.上鎖解鎖過程

  • 當一個線程調用鎖的acquire()方法獲得鎖時,鎖就進入“locked”狀態。其他線程需要等待這個鎖釋放才能鎖定。
  • 每次只有一個線程可以獲得鎖。如果此時另一個線程試圖獲得這個鎖,該線程就會變爲“blocked”狀態,稱爲“阻塞”,直到擁有鎖的線程調用鎖的release()方法釋放鎖之後,鎖進入“unlocked”狀態。
  • 線程調度程序從處於同步阻塞狀態的線程中選擇一個來獲得鎖,並使得該線程進入運行(running)狀態

2.鎖的好處:

  • 確保了某段關鍵代碼只能由一個線程從頭到尾完整地執行
  • 保證代碼正常的情況下,儘量給最少的代碼的上鎖,因爲上鎖的代碼只能以單線程執行,效率低

3. 鎖的壞處:

  • 阻止了多線程併發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地下降了
  • 由於可以存在多個鎖,不同的線程持有不同的鎖,並試圖獲取對方持有的鎖時,可能會造成死鎖

3.死鎖與解決 

 在線程間共享多個資源的時候,如果兩個線程分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖。

import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        # 對mutexA上鎖
        mutexA.acquire()

        # mutexA上鎖後,延時1秒,等待另外那個線程 把mutexB上鎖
        print(self.name+'----do1---up----')
        time.sleep(1)

        # 此時會堵塞,因爲這個mutexB已經被另外的線程搶先上鎖了
        mutexB.acquire()
        print(self.name+'----do1---down----')
        mutexB.release()

        # 對mutexA解鎖
        mutexA.release()

class MyThread2(threading.Thread):
    def run(self):
        # 對mutexB上鎖
        mutexB.acquire()

        # mutexB上鎖後,延時1秒,等待另外那個線程 把mutexA上鎖
        print(self.name+'----do2---up----')
        time.sleep(1)

        # 此時會堵塞,因爲這個mutexA已經被另外的線程搶先上鎖了
        mutexA.acquire()
        print(self.name+'----do2---down----')
        mutexA.release()

        # 對mutexB解鎖
        mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

避免死鎖

  • 程序設計時要儘量避免(銀行家算法)
  • 添加超時時間等
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章