Python 多線程死鎖的示例

在 Python 中多線程中爲了防止資源競爭而出現問題,提供了鎖的機制,當一個線程操作資源時進行加鎖,操作完畢後釋放鎖,這樣其他線程就不會同時操作資源導出出現異常。

在 Python 多線程中注意是兩種鎖:互斥鎖和遞歸鎖
那麼它們有什麼區別呢?
互斥鎖:
一旦一個線程獲得一個互斥鎖,會阻塞隨後嘗試獲得鎖的線程,直到它被釋放;任何線程都可以釋放它。
遞歸鎖:
遞歸鎖必須由獲取它的線程釋放。一旦線程獲得了遞歸鎖,同一個線程再次獲取它將不阻塞;線程必須在每次獲取它時釋放一次。

雖然鎖可以防止程序出問題,但是使用鎖不得當是很容易出現死鎖。

死鎖 : 當線程A持有獨佔鎖a,並嘗試去獲取獨佔鎖b的同時,線程B持有獨佔鎖b,並嘗試獲取獨佔鎖a的情況下,就會發生AB兩個線程由於互相持有對方需要的鎖,而發生的阻塞現象,我們稱爲死鎖。通俗的將就是一種相互等待無法結束的現象。

下面就演示一下在 Python 中可能出現的死鎖

1、互斥鎖使用過程中在子函數內再次加鎖,導致死鎖。
import threading
import time

num = 0
# 注意:在普通的互斥鎖中,在同一個線程內如果存在一個函數多次獲得鎖,就會出現死鎖。注意:遞歸鎖就不會。
llock = threading.Lock() # 創建一個互斥鎖

def add_num(lock):
    global num
    lock.acquire()
    num += 1
    lock.release()

def run1(n, lock):
    global num
    print('task ',n)
    time.sleep(1)
    lock.acquire()
    for i in range(1000000):
        # lock.acquire()
        add_num(lock)
        # lock.release()
    lock.release()
    print(num)

def run2(n, lock):
    global num
    print('task ',n)
    time.sleep(1)
    lock.acquire()
    for i in range(1000000):
        # lock.acquire()
        add_num(lock)
        # lock.release()
    lock.release()
    print(num)

if __name__ == '__main__':
    start_time = time.time()
    # 定義多線程 如果我們傳入的是互斥鎖,那麼就會出現死鎖,
    t1 = threading.Thread(target=run1, args=('線程A',  llock))
    t2 = threading.Thread(target=run2, args=('線程B', llock))
    # 啓動多線程
    t1.start()
    t2.start()
    # 設置主線程等待子線程完成後退出,設置完後主線程會阻塞在這裏
    t1.join()
    t2.join()
    print('消耗時間:', time.time() - start_time)
2、無論使用遞歸鎖還是互斥鎖,當出現互相等待時,導致死鎖。
import threading
import time
'''
互相等待的死鎖:
當鎖出現互相等待的時候就會出現死鎖。例如方法1 先將a加鎖,並且想獲取b的鎖,然而此時方法2先將b加鎖,再將b加鎖。由於方法1沒有得到b的鎖因此不會釋放。導致方法2無法獲取b的鎖。出現死鎖。
run1(a, b)
acquire(a) 將a加鎖
acquire(b) 將b加鎖

run2(, b)
acquire(b) 將b加鎖,前提是其他方法先釋放b的鎖
acquire(a)
'''

a = 0
b = 0
# 創建一個互斥鎖,就算使用遞歸鎖,這種相互等待的也會出現死鎖
lockA = threading.Lock()
lockB = threading.Lock()

def run1(n):
    global a
    global b
    print('task ',n)
    lockA.acquire()
    a += 1
    time.sleep(1)
    lockB.acquire()
    b += 1
    lockB.release()
    lockA.release()
    print(a)

def run2(n):
    global a
    global b
    print('task ',n)
    lockB.acquire()
    b += 1
    lockA.acquire()
    a += 1
    lockA.release()
    lockB.release()
    print(a)

if __name__ == '__main__':
    start_time = time.time()
    # 定義多線程
    t1 = threading.Thread(target=run1, args=('線程A',  ))
    t2 = threading.Thread(target=run2, args=('線程B', ))
    # 啓動多線程
    t1.start()
    t2.start()
    # 設置主線程等待子線程完成後退出,設置完後主線程會阻塞在這裏
    t1.join()
    t2.join()
    print('消耗時間:', time.time() - start_time)
3、通過代碼來看互斥鎖和遞歸鎖的區別
import threading
import time

num = 0
# 創建一個遞歸鎖 在同一個線程裏面可以連續調用多次acquire()獲得鎖,但是要保證調用acquire的次數和release的次數保持一致。
# 注意:在普通的互斥鎖中,在同一個線程內如果存在一個函數多次獲得鎖,就會出現死鎖。遞歸鎖就不會。下面具體例子說明
rlock = threading.RLock() # 創建一個遞歸鎖
llock = threading.Lock() # 創建一個互斥鎖

def add_num(lock):
    global num
    lock.acquire()
    num += 1
    lock.release()

def run1(n, lock):
    global num
    print('task ',n)
    time.sleep(1)
    lock.acquire()
    for i in range(1000000):
        # lock.acquire()
        add_num(lock)
        # lock.release()
    lock.release()
    print(num)

def run2(n, lock):
    global num
    print('task ',n)
    time.sleep(1)
    lock.acquire()
    for i in range(1000000):
        # lock.acquire()
        add_num(lock)
        # lock.release()
    lock.release()
    print(num)

if __name__ == '__main__':
    start_time = time.time()
    # 定義多線程 如果我們傳入的是互斥鎖,那麼就會出現死鎖,
    # t1 = threading.Thread(target=run1, args=('線程A',  llock))
    # t2 = threading.Thread(target=run2, args=('線程B', llock))
    # 傳入遞歸鎖
    t1 = threading.Thread(target=run1, args=('線程A',  rlock))
    t2 = threading.Thread(target=run2, args=('線程B', rlock))
    # 啓動多線程
    t1.start()
    t2.start()
    # 設置主線程等待子線程完成後退出,設置完後主線程會阻塞在這裏
    t1.join()
    t2.join()
    print('消耗時間:', time.time() - start_time)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章