锁是非常重要的技术,并发的时候,都在争抢同一个资源的时候,这个时候就需要考虑是否使用锁,锁有好处和坏处
,有了锁,并发的运行效率降低,耗时降低,但没有锁就得不到你想要的结果
用锁也是有一些原则的,尽量少的使用锁,在必须使用的地方再使用,
尽量短的使用锁,锁里面的语句能少则少(比如高速公路过关卡,收费站人员故意拖慢时间,后面等的时间就增加了)
所以争抢一个资源,就需要尽量快的去使用,然后把锁释放掉,一定避免死锁,死锁出现了就非常头疼,死锁 出现了,如果不kill进程,基本就没有好办法了,应该尽一切可能避免死锁
锁的使用很简单,两个方法acquire,release
这三处去释放其实都不优雅
应该使用try fianlly,还有一种是with语法
拿到锁一定要释放,否则就是死锁
用了锁,效率就低下,这是一定的,碰到锁,原来并行的就变成串行的了,大家都访问一个资源的时候,加锁的地方,就是一个瓶颈,使用锁了,就需要尽快释放掉,
阻塞和非阻塞的时候要小心使用,阻塞的时候,考虑很简单,下一句不能执行,非阻塞的就会有一些问题,
下面代码就是,开启10个任务对象,每一个任务对象,除了按名字,还需要加个lock,每个任务对象有自己的锁
下面开启5个线程,会遍历当前的线程列表,等于有10个任务交给5个线程去跑,5个线程里面,遍历任务列表,如果在去任务对象 的锁上去请求,如果请求到了,则打
如果请求到了,则打印
请求失败,打印这个
第一个线程起来,遍历任务列表的时候,先遍历任务列表的第一个任务,尝试对第一个任务获取锁,获取成功后可以在里面执行,一直在处理没释放
如果现在第二个线程启动了,遍历的时候先访问第一个任务,只要第一个线程在第一个任务上没有释放,所有后面的线程都阻塞了,就没有什么可以执行的了
如果采用非阻塞行为,第一个线程在第一个任务上,在访问的时候,获得了一个任务的锁,第二个再去这个任务的锁上获取锁,获取失败,这样就可以去拿第二个任务,现在没有人管第二个任务,就获得第二个任务的锁
这时候第三个线程启动了,启动之后,依然去遍历任务列表,第一个,第二个任务都获得锁失败,就会尝试获得第三个任务的锁,结果获取到了,如果把非阻塞效果,false改为true,这样就只能跑一个线程,因为其他线程获取不到锁,就全部阻塞了
想清楚,true和false的差异
非阻塞的时候,有可能acquire返回true和false,如果是永久阻塞得到的结果一定是true,否则永远都不改,所以写代码只要考虑下面的即可
非阻塞就需要考虑,拿到了怎么样,没拿到怎么样,大多数情况写阻塞,简单快速
可重入锁,也称为递归锁
有owner和count,现在是未上锁的
在这之前没人拿到锁
数字加1了
lock。acquire永久阻塞的方式,依然能拿到
现在拿了两次锁没有释放掉
说明释放的时候,count是减少的
两个release就没有了
没有获取的锁,release就报错,也就是不可以多次release
锁三次,release两次,就剩一个,默认acquire是阻塞的
当前环境下,可以连续获得锁
拿到几次锁就需要释放几次锁,总共锁了4次,释放了两次,是2,没有问题
为什么拿不到锁,就是因为跨线程
现在这个锁到线程调用前都没有acquire
count=0
现在打印出来是0
修改一下代码,再次打印,再另一个线程可以获得锁
现在变成这样
拿三次,释放三次,看看里面的能否打印出来
如果在另一个线程要用同一个锁对象的时候,只要发现这个锁对象的count=0,才有资格申请,如果count不等于0,就申请不到
现在这个owner是线程id
查看rlock代码
这就是取线程ID,把线程ID变成自己的owner,把ID给owner,然后count+1
如果线程占用了,count一定会+1,也就是count=0的时候,才允许切换,另一个线程才能获得这把锁,线程获得锁之后,owner就会换成当前线程ID,因为getident是拿的当前线程ID
Rlock是与线程相关的锁,实验发现,Rlock只要调用rcquire,首先看count是什么,如果count是0,就允许当前线程ID替换成当前owner,替换了一次,count+1,之后,如果有别的线程过来访问这把锁,count!=0,不允许访问,访问失败,阻塞还是不阻塞跟你后面写的acquire参数有关,当前线程把获得的所有次数,全部release完,count清零,其他线程才能获得这把锁,其他如果某一个线程获得了这把锁,立即将owner改成自己的线程ID,在哪个线程获得这个锁的线程内,可以连续多次acquire,也就是可以连续多次获得锁,
其他线程包括主线程,只要是某一个工作线程获得这把锁后,包括主线程,都不能碰这把锁,因为这把锁owner不是你。
rlock是跟线程相关的,(现在学的跟线程相关的锁,thread.lock,RLock)
在同一个线程里,可以多次获取这把锁,但是使用锁的基本原则没有改变,用了多少次锁,就要release多少次,否则不会count=0,count!=0,其他线程根本没有资格获取这把锁,所以相当于又造成死锁现象,
主线程就卡住了,总之,这把锁被某个线程占了以后,只要不清0,其他线程访问,全部都获得不到,如果用的阻塞方式,就阻塞住了
可重入锁,也称为递归锁,是线程相关的锁,由owner来决定,当前是哪个线程获得这把锁,线程A获得锁之后,就可以在自己线程 内多次获得这个锁,都不会阻塞,要求每个线程acquire多少次,就需要release多少次,
延迟三秒执行,后面的函数,可以lambdablocking=false不阻塞
跨线程要报错,报错在于,锁根本就不是你的
可重入锁和线程相关,可以在一个线程中获得这个锁,一旦获得锁,属主就归你了,可以在线程中多次获得这个锁,当锁未释放完,其他线程获取锁就会阻塞,也可以(按照你指定的不阻塞也可以),阻塞必须到持有这把锁的线程释放掉,release完,count为0了,其他就可以抢这把锁 了。
所有的锁应该在使用完之后,acquire多少次,release多少次
conditon,条件满足该怎么样,构造方法传入lock即可,(event,就是条件满足,工人都完成了给boss看到了)
默认传入RLock对象
等待或者超时
通知指定的几个
、
通知所有
condition就是条件变化,通知所有人,这样所有人看到这个状态变化的时候就有反应,都在等状态变化,通知正在等待中的线程
condition是用于生产者和消费者模型的,大多数数据都会有产出,就是一个数据生产者,数据处理完,还需要输出,大多数程序都需要输出,所以可以认为大多数程序都是生产者,有了生产者,一个程序处理完,是否还需要其他程序参与,如果要其他程序参与,这个程序就要获得,前面的程序输出的结果,这就变成了生产者和消费者模型,
以前是用队列,来进行解耦
生产者和消费者模型,还有一种方式是,通知的模式,就算生产者把数据放在队列,那么消费者如何得知,还是需要队列的。以前消费者是看生产者数据是否准备好了,现在是消费者看队列里是否有数据要处理,消费者怎么看队列数据已经好了,时不时看看,或者好了通知你
两种方式,要么主动去查,要么去通知你。生产者和消费者用这种通知机制并不能代替缓冲,数据来了先缓冲一下,但是并不能代替解耦的东西
condition应用在生产者和消费者模型的地方
每一个都有适用,场景,必须掌握
自产自用,生产有生产速度,消费有消费速度,event其实没什么业务能力,就跟time.sleep一样
等待相应时间
通过这样就在模拟,消费者消费速度,每隔0.5秒处理一次数据,现在两者数据是不匹配的
Dspatcher()初始化,然后开启两个线程来使用,让消费者先来,先阻塞一下
消费者0.5秒一次,生产者1秒一次
报错,补一个括号
现在运行,现在就是,读了一次数据,再一次读取的时候,由于生产慢,等于做了一次无用功
一般都是先让消费者先启动,因为避免生产者先启动,然后丢失数据,对我们来说数据重要的话,消费者先启动
拿到一个以后,其他两个都是none,queue就是拿了以后,其他没的拿了
如果要一个数据分发给多个人,这一句就不要了
现在访问就都一样了
生产者和消费者的速度,是难以平衡的
默认使用RLock,
做好之后就要通知大家,背后的数据是要等待着的,使用数据的人应该调用wait,生产者生产完数据应该notify,每生产一个数据,应该告诉大家,
可以使用with语法,应该是消费者等生产者通知,
数据生产好了就要通知大家
用了condition就用到锁了,有个acquire,一定有release,就一定实现了上下文管理,
通知下面线程不需要等了,都来拿数据,拿到数据消费完之后,就等下一次的通知
把这个event提到外面
生产的时候可以放外面去,尽量让锁定的时间端一点,生产好了,发通知告诉消费者
生产者生产10次数据就停止了,消费者在就等待了
如果消费者速度和生产速度差太多,开启多个消费者,用queue,一个拿一个数据来进行处理
这种通知机制,告诉外面,一份数据多人用,也称广播机制
这样就变成5个
通知两个,称为多播,每个任务派两个去做即可
一般来讲是消费者先启动
一对全部是广播,一对多是多播
condition
condition 是用在生产者和消费者中,解决生产者消费者速度匹配的问题,通知机制,强制把速度调成一样了。
使用condition 必须先acquire。用完了要release,内部使用了锁,默认使用RLock,使用锁的最好的方式是with语法,能够保证锁最后一定会被release。
消费者会等待数据准备好,条件成立了就wait,一般都是永久阻塞的,等条件成熟了,就取消
生产者生产好消息,对消息者发通知,可以使用notify或者notify_all方法
消息队列也有类似的通知机制,尤其是第三方消息队列,都提供了
Rlock的东西其实就是condition也要用,Rlock是线程相关的,在线程中可重入,一定会标记owner是谁,count是多少,另一个线程要用,必须count清零,抢占这把锁,一旦抢占,当前线程可以再次重入获得锁,但是另外的线程要用,就需要清0才可以
lock可以管理你这个线程的,谁占用就是谁的,不释放,别人是用不了这把锁的,要么永久阻塞,跟线程无关,只是同一个lock对象
跟even对象一样
self.condition,self.event这样是同一个对象