在 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)