python中最常用的5种线程锁 前言 本文将继续围绕 threading 模块讲解,基本上是纯理论偏多 对于日常开发者来讲很少会使用到本文的内容,但是对框架作者等是必备知识,同时也是高频的面试常见问题 线程安全 线程安全是多线程或多进程编程中的一个概念,在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。 线程安全的问题最主要还是由线程切换导致的,比如一个房间(进程)中有10颗糖(资源),除此之外还有3个小人(1个主线程、2个子线程),当小人A吃了3颗糖后被系统强制进行休息时他认为还剩下7颗糖,而当小人B工作后又吃掉了3颗糖,那么当小人A重新上岗时会认为糖还剩下7颗,但是实际上只有4颗了。 上述例子中线程A和线程B的数据不同步,这就是线程安全问题,它可能导致非常严重的意外情况发生,我们按下面这个示例来进行说明。 下面有一个数值num初始值为0,我们开启2条线程:线程1对num进行一千万次+1的操作线程2对num进行一千万次-1的操作 结果可能会令人咋舌,num最后并不是我们所想象的结果0: 上面这就是一个非常好的案例,想要解决这个问题就必须通过锁来保障线程切换的时机。 需要我们值得留意的是,在Python基本数据类型中list、tuple、dict本身就是属于线程安全的,所以如果有多个线程对这3种容器做操作时,我们不必考虑线程安全问题。 锁的作用 锁是Python提供给我们能够自行操控线程切换的一种手段,使用锁可以让线程的切换变的有序。 一旦线程的切换变的有序后,各个线程之间对数据的访问、修改就变的可控,所以若要保证线程安全,就必须使用锁。 threading模块中提供了5种最常见的锁,下面是按照功能进行划分:同步锁:lock(一次只能放行一个)递归锁:rlock(一次只能放行一个)条件锁:condition(一次可以放行任意个)事件锁:event(一次全部放行)信号量锁:semaphore(一次可以放行特定个) 1、Lock() 同步锁 基本介绍 Lock锁的称呼有很多,如:同步锁互斥锁 它们是什么意思呢?如下所示:互斥指的是某一资源同一时刻仅能有一个访问者对其进行访问,具有唯一性和排他性,但是互斥无法限制访问者对资源的访问顺序,即访问是无序的同步是指在互斥的基础上(大多数情况),通过其他机制实现访问者对资源的有序访问同步其实已经实现了互斥,是互斥的一种更为复杂的实现,因为它在互斥的基础上实现了有序访问的特点 下面是threading模块与同步锁提供的相关方法: 方法描述threading.Lock()返回一个同步锁对象lockObject.acquire(blocking=True, timeout=1)上锁,当一个线程在执行被上锁代码块时,将不允许切换到其他线程运行,默认锁失效时间为1秒lockObject.release()解锁,当一个线程在执行未被上锁代码块时,将允许系统根据策略自行切换到其他线程中运行lockObject.locaked()判断该锁对象是否处于上锁状态,返回一个布尔值 使用方式 同步锁一次只能放行一个线程,一个被加锁的线程在运行时不会将执行权交出去,只有当该线程被解锁时才会将执行权通过系统调度交由其他线程。 如下所示,使用同步锁解决最上面的问题: 这样这个代码就完全变成了串行的状态,对于这种计算密集型I/O业务来说,还不如直接使用串行化单线程执行来得快,所以这个例子仅作为一个示例,不能概述锁真正的用途。 死锁现象 对于同步锁来说,一次acquire()必须对应一次release(),不能出现连续重复使用多次acquire()后再重复使用多次release()的操作,这样会引起死锁造成程序的阻塞,完全不动了,如下所示: with语句 由于threading.Lock()对象中实现了__enter__()与__exit__()方法,故我们可以使用with语句进行上下文管理形式的加锁解锁操作: 2、RLock() 递归锁 基本介绍 递归锁是同步锁的一个升级版本,在同步锁的基础上可以做到连续重复使用多次acquire()后再重复使用多次release()的操作,但是一定要注意加锁次数和解锁次数必须一致,否则也将引发死锁现象。 下面是threading模块与递归锁提供的相关方法: 方法描述threading.RLock()返回一个递归锁对象lockObject.acquire(blocking=True, timeout=1)上锁,当一个线程在执行被上锁代码块时,将不允许切换到其他线程运行,默认锁失效时间为1秒lockObject.release()解锁,当一个线程在执行未被上锁代码块时,将允许系统根据策略自行切换到其他线程中运行lockObject.locaked()判断该锁对象是否处于上锁状态,返回一个布尔值 使用方式 以下是递归锁的简单使用,下面这段操作如果使用同步锁则会发生死锁现象,但是递归锁不会: with语句 由于threading.RLock()对象中实现了__enter__()与__exit__()方法,故我们可以使用with语句进行上下文管理形式的加锁解锁操作: 3、Condition() 条件锁 基本介绍 条件锁是在递归锁的基础上增加了能够暂停线程运行的功能。并且我们可以使用wait()与notify()来控制线程执行的个数。 注意:条件锁可以自由设定一次放行几个线程。 下面是threading模块与条件锁提供的相关方法: 方法描述threading.Condition()返回一个条件锁对象lockObject.acquire(blocking=True, timeout=1)上锁,当一个线程在执行被上锁代码块时,将不允许切换到其他线程运行,默认锁失效时间为1秒lockObject.release()解锁,当一个线程在执行未被上锁代码块时,将允许系统根据策略自行切换到其他线程中运行lockObject.wait(timeout=None)将当前线程设置为“等待”状态,只有该线程接到“通知”或者超时时间到期之后才会继续运行,在“等待”状态下的线程将允许系统根据策略自行切换到其他线程中运行lockObject.wait_for(predicate, timeout=None)将当前线程设置为“等待”状态,只有该线程的predicate返回一个True或者超时时间到期之后才会继续运行,在“等待”状态下的线程将允许系统根据策略自行切换到其他线程中运行。注意:predicate参数应当传入一个可调用对象,且返回结果为bool类型lockObject.notify(n=1)通知一个当前状态为“等待”的线程继续运行,也可以通过参数n通知多个lockObject.notify_all()通知所有当前状态为“等待”的线程继续运行 使用方式 下面这个案例会启动10个子线程,并且会立即将10个子线程设置为等待状态。 然后我们可以发送一个或者多个通知,来恢复被等待的子线程继续运行: with语句 由于threading.Condition()对象中实现了__enter__()与__exit__()方法,故我们可以使用with语句进行上下文管理形式的加锁解锁操作: 4、Event() 事件锁 基本介绍 事件锁是基于条件锁来做的,它与条件锁的区别在于一次只能放行全部,不能放行任意个数量的子线程继续运行。 我们可以将事件锁看为红绿灯,当红灯时所有子线程都暂停运行,并进入“等待”状态,当绿灯时所有子线程都恢复“运行”。 下面是threading模块与事件锁提供的相关方法: 方法描述threading.Event()返回一个事件锁对象lockObject.clear()将事件锁设为红灯状态,即所有线程暂停运行lockObject.is_set()用来判断当前事件锁状态,红灯为False,绿灯为TruelockObject.set()将事件锁设为绿灯状态,即所有线程恢复运行lockObject.wait(timeout=None)将当前线程设置为“等待”状态,只有该线程接到“绿灯通知”或者超时时间到期之后才会继续运行,在“等待”状态下的线程将允许系统根据策略自行切换到其他线程中运行 使用方式 事件锁不能利用with语句来进行使用,只能按照常规方式。 如下所示,我们来模拟线程和红绿灯的操作,红灯停,绿灯行: 5、Semaphore() 信号量锁 基本介绍 信号量锁也是根据条件锁来做的,它与条件锁和事件锁的区别如下:条件锁:一次可以放行任意个处于“等待”状态的线程事件锁:一次可以放行全部的处于“等待”状态的线程信号量锁:通过规定,成批的放行特定个处于“上锁”状态的线程 下面是threading模块与信号量锁提供的相关方法: 方法描述threading.Semaphore()返回一个信号量锁对象lockObject.acquire(blocking=True, timeout=1)上锁,当一个线程在执行被上锁代码块时,将不允许切换到其他线程运行,默认锁失效时间为1秒lockObject.release()解锁,当一个线程在执行未被上锁代码块时,将允许系统根据策略自行切换到其他线程中运行 使用方式 以下是使用示例,你可以将它当做一段限宽的路段,每次只能放行相同数量的线程: with语句 由于threading.Semaphore()对象中实现了__enter__()与__exit__()方法,故我们可以使用with语句进行上下文管理形式的加锁解锁操作: 锁关系浅析 上面5种锁可以说都是基于同步锁来做的,这些你都可以从源码中找到答案。 首先来看RLock递归锁,递归锁的实现非常简单,它的内部会维护着一个计数器,当计数器不为0的时候该线程不能被I/O操作和时间轮询机制切换。但是当计数器为0的时候便不会如此了: 而Condition条件锁的内部其实是有两把锁的,一把底层锁(同步锁)一把高级锁(递归锁)。 低层锁的解锁方式有两种,使用wait()方法会暂时解开底层锁同时加上一把高级锁,只有当接收到别的线程里的notfiy()后才会解开高级锁和重新上锁低层锁,也就是说条件锁底层是根据同步锁和递归锁的不断切换来进行实现的: Event事件锁内部是基于条件锁来做的: Semaphore信号量锁内部也是基于条件锁来做的: 基本练习题 条件锁的应用 需求:一个空列表,两个线程轮番往里面加值(一个加偶数,一个加奇数),最终让该列表中的值为 1 – 100 ,且是有序排列的。 事件锁的应用 有2个任务线程来扮演李白和杜甫,如何让他们一人一句进行对答?文本如下:杜甫:老李啊,来喝酒!李白:老杜啊,不喝了我喝不下了!杜甫:老李啊,再来一壶?杜甫:…老李?李白:呼呼呼…睡着了..
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/53079.html