以python为例来理解多线程中的同步锁&死锁&递归锁

同步锁

首先,我们先了解在使用多线程的时候,什么情况下需要加锁?
那是因为在不同的线程,我们可能会需要对同一个变量进行修改,这个时候就会出现资源抢占的问题,比如在线程A中,对变量X的修改还未执行完毕时,这个时候线程B也要对X进行修改,此时线程B就会把X抢占过来,那么线程A中对X的修改就会被中断,导致修改不成功。
比如,我们下面这个例子,初设化n=0,我们启动10个线程,每个线程都对n进行1000000次+1的操作,那么预期的结果n应该等于10000000

from threading import Thread


def add_n():
    global n
    for i in range(1000000):
        n += 1

# 开始添加线程
threads = []
# target:函数,args:函数的参数,以元组形式
for i in range(10):
    threads.append(Thread(target=add_n))

n = 0

for t in threads:
    # 设置为守护线程:主线程运行完毕之后,子线程仍然继续运行
    t.setDaemon(True)
    t.start()  # 所有子线程同时启动
# 等待所有子线程运行完毕之后,再运行主程序
for t in threads:
    t.join()
    
print("n = ", n)

但是,可以看到,结果并不是1000000,这就是多线程资源抢占造成的后果

n =  3434881

那么问题来了,如何解决呢?这个时候就需要用到同步锁了,在对n进行+1时,加上一把同步锁,等操作完成后再释放锁,这样就保证在加锁期间,只有一个线程在执行n+=1操作,其他线程只能等待锁的释放之后才能执行该操作。

from threading import Thread, Lock

lock = Lock()

def add_n():
    global n
    for i in range(1000000):
        lock.acquire()  # 
        n += 1
        lock.release()

# 开始添加线程
threads = []
# target:函数,args:函数的参数,以元组形式
for i in range(10):
    threads.append(Thread(target=add_n))

n = 0

for t in threads:
    # 设置为守护线程:主线程运行完毕之后,子线程仍然继续运行
    t.setDaemon(True)
    t.start()  # 所有子线程同时启动
# 等待所有子线程运行完毕之后,再运行主程序
for t in threads:
    t.join()
    
print("n = ", n)

这个时候,加上同步锁之后,得到的n就如我们预期一样了。

n =  10000000

死锁

在给多线程加锁的时候,需要特别注意一点就是防止出现死锁。
在下面的例子中,就出现了死锁的情况,我们来分析一下为什么出现了死锁?

  1. A拿了一个苹果,并把苹果锁住了;
  2. B拿了一个香蕉,并把香蕉锁住了;
  3. A现在想再拿个香蕉,但必须得等待B释放这个香蕉;
  4. B同时想要再拿个苹果,这时候它也得等待A释放苹果;
  5. 这样就是陷入了僵局,出现了死锁。
from threading import Thread, Lock
import time

lock_apple = Lock()
lock_banana = Lock()

def fun1():

    lock_apple.acquire()  # 如果锁被占用,则阻塞在这里,等待锁的释放

    print ("线程 %s , 想拿: %s--%s" %(t.name, "苹果",time.ctime()))

    lock_banana.acquire()
    print ("线程 %s , 想拿: %s--%s" %(t.name, "香蕉",time.ctime()))
    lock_banana.release()
    lock_apple.release()


def fun2():

    lock_banana.acquire()
    print ("线程 %s , 想拿: %s--%s" %(t.name, "香蕉",time.ctime()))
    time.sleep(0.1)

    lock_apple.acquire()
    print ("线程 %s , 想拿: %s--%s" %(t.name, "苹果",time.ctime()))
    lock_apple.release()

    lock_banana.release()

def run():
    fun1()
    fun2()

# 开始添加线程
threads = []
# target:函数,args:函数的参数,以元组形式
for i in range(10):
    threads.append(Thread(target=run))

for t in threads:
    # 设置为守护线程:主线程运行完毕之后,子线程仍然继续运行
    t.setDaemon(True)
    t.start()  # 所有子线程同时启动
for t in threads:
    t.join()

程序会一直卡在这里不动了

线程 Thread-1 , 想拿: 苹果--Wed Aug 28 12:19:40 2019
线程 Thread-1 , 想拿: 香蕉--Wed Aug 28 12:19:40 2019
线程 Thread-2 , 想拿: 香蕉--Wed Aug 28 12:19:40 2019
线程 Thread-2 , 想拿: 苹果--Wed Aug 28 12:19:40 2019

递归锁

解决死锁,就需要用到递归锁了。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

from threading import Thread, RLock
import time

lock = RLock()

def fun1():

    lock.acquire()  # 如果锁被占用,则阻塞在这里,等待锁的释放

    print ("线程 %s , 想拿: %s--%s" %(t.name, "苹果",time.ctime()))

    lock.acquire()
    print ("线程 %s , 想拿: %s--%s" %(t.name, "香蕉",time.ctime()))
    lock.release()
    lock.release()


def fun2():

    lock.acquire()
    print ("线程 %s , 想拿: %s--%s" %(t.name, "香蕉",time.ctime()))
    time.sleep(0.1)

    lock.acquire()
    print ("线程 %s , 想拿: %s--%s" %(t.name, "苹果",time.ctime()))
    lock.release()

    lock.release()

def run():
    fun1()
    fun2()

# 开始添加线程
threads = []
# target:函数,args:函数的参数,以元组形式
for i in range(10):
    threads.append(Thread(target=run))

for t in threads:
    # 设置为守护线程:主线程运行完毕之后,子线程仍然继续运行
    t.setDaemon(True)
    t.start()  # 所有子线程同时启动
for t in threads:
    t.join()

欢迎关注同名公众号:“我就算饿死也不做程序员”。
交个朋友,一起交流,一起学习,一起进步。在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值