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