2019/12/22 01-Lock锁

在这里插入图片描述
凡是存在共享资源争抢的地方都可以使用锁,从而保证只有一个使用者可以完全使用这个资源。当你抢到资源,只为你所用,其他人不可用的时候,就要使用锁,(比如打饭的其他人可以看到你在选什么资源,这是比较松的锁,如果连看都不能看就是比较严格的锁)看可以,但是修改不行

一旦抢到资源,肯定不轻易让给别人使用,要引入锁,锁是为了解决在并发过程中解决一个人挤到窗口拿到属于自己的饭,一般情况保障当下一个资源只能有一个人使用者
争抢中避免出现,一个线程很忙,其他都很闲,锁是放在有共享资源争抢的地方。因为资源独一份,资源少,访问者多,就出现了高并发的问题。
需要锁是因为,如果你请求到了数据,事情没做完,就被人挤跑了,要保证请求到资源的时间内,保证做的事情不能被打断

例子:
在这里插入图片描述
用这种方式就把参数穿进去,进行工作在这里插入图片描述

工人生产了需要有一个共同的箱子装起来,生产杯子的时候要判断下生产完了没有,最后看下杯子数在这里插入图片描述在这里插入图片描述
跑10个线程并行,生产的杯子塞到一个cup里在这里插入图片描述
但是结果好像有点超出,有点增加成本了,浪费计算机的资源
在这里插入图片描述
把这一句去掉试试
在这里插入图片描述
time.sleep不能随便去掉,是为了看效果的
在这里插入图片描述超出的问题在于if 语句这一块,当某一个线程,比如工人1号,进入循环,就判断当前箱子里是多少个杯子,假设做999个进来,1号进来,999,不break,继续往下走,走到下面线程切换了,2号工人进来,999个,在这里插入图片描述2号工人进来,999个,走到这,准备生产,就切换了,切换到8号工人,看到里面是999个,还差一个,就也生产去了,生产了一个,线程结束,切换到下一个线程,9号工人,9号工人看到1000个,就不在生产了,但是1号二号不做判断继续生产在这里插入图片描述
所以生产前一次再判断一次,但是这是两条语句,判断了,并不代表连续着执行,多线程谁也保证不了,,可能多条线程判断是999,生产,这样有可能都一起生产,只要是发生切换的人,都以为是999个,增加就多了,现在的python语句不保证前一句执行了后一句是跟着执行的,都可能会被线程切开在这里插入图片描述
但是event不适合再这种场景,现在是认为这些事情不可打乱,用flag =true,flag就变成局部变量了,跟外面无关
在这里插入图片描述

先每一个上来都认为目标是false,flag=false,但是这样也是会切换线程的,是有可能超标的
在这里插入图片描述
现在使用锁,是自己一把锁,还是每个人一把锁,这写一把锁即可
在这里插入图片描述在这里插入图片描述在这里插入图片描述
拿到锁了还可以释放release掉,可以阻塞使用也可以非阻塞使用在这里插入图片描述
锁的概念,就是再这段时间中,不准你碰在这里插入图片描述
锁的,未锁的,就是释放掉了在这里插入图片描述
未锁住的锁,直接release,会报错在这里插入图片描述
true可以认为获得了锁的控制权,再有一次对锁进行请求,就被迫阻塞在这里插入图片描述
release完了以后变成一种unlock状态,unlocked的时候再次release是不可以的,unlocked,经过acquire就锁住了,再次acquire就阻塞了,设定永久阻塞就一直等,如果有时间,就返回false,表示没拿到锁

不管等还是不等,只要拿到锁就返回true,没获得成功就返回false,永久等写none
在这里插入图片描述
现在是locked,不管阻塞还是非阻塞,用acquire目的就是拿到锁,只要没获得,返回值就一定是false
在这里插入图片描述
现在拿到锁了,但是其他来的人都会被阻塞住,就需要再哪个合适的地方release掉在这里插入图片描述
假设1号线程先执行了这一句,到达下面一句的时候切换成2号线程的时候,2号线程到这一句的时候被迫等待,不能往下走,现在又切换成1号线程,向下走,在这里插入图片描述
1号线程到下面,还没有生产,就在24行release,2号线程就切换来了,看锁开了,在这里插入图片描述,2号线程就切换来了,看锁开了,拿到锁,继续向下执行,判断不大于,999个,就又到24行释放锁了,这样有可能线程又切换到1号线程上了在这里插入图片描述
现在,只是测算24行位置的release,现在线程切换到2号线程在这里插入图片描述
2号线程可能刚走到这句,又切换了1号线程
在这里插入图片描述
1号线程又到了这一句,就又锁上了,锁上之后又切换到2行线程
在这里插入图片描述锁上之后又切换到2行线程,还在生产杯子在这里插入图片描述
所以release在这个24行位置是根本不能保证的,肯定会多生产在这里插入图片描述
假设还是999的情况,现在release的位置放到29行,1号线程,执行锁,刚好执行到生产,就切换了在这里插入图片描述
切换到2号线程,那么1号的锁还没释放,就被阻塞了在这里插入图片描述
1号线程切换回来之后,准备生产一个杯子,到达29行释放一刹那,线程切换了,锁被拿掉了在这里插入图片描述
轮到2号线程执行,就锁了,往下走,好像已经1号线程,生产好1000个了,已经满足要求了在这里插入图片描述
只要在这一段内,保证只有一个线程执行就够了,1号线程在执行这一段的时候,2号线程是进不去这一段的,之前放在24行的release,判断和生产是断开了,现在是判断和生产联合在一起了在这里插入图片描述
现在就达到要求了
在这里插入图片描述
试试放在33行,运行试试
在这里插入图片描述
中止了
在这里插入图片描述
因为在这里就break了,下面的releas就执行不了了
在这里插入图片描述
加了锁最大问题就是,别人用不了,因为把控制权交给你,你没让出去锁,别人就用不了,这就是一种死锁现象,谁获得锁,最后一定要释放掉,就会出现问题,
在这里插入图片描述
实际上做分析,就从999到最后开始,再造一个杯子会怎么样,就是测试临界状态的,发生切换,会发生什么现象,if语句不保证下面语句跟着执行,是会进行切换的
在这里插入图片描述
你访问资源,别人就不允许使用,就是排他锁,,还有一种是共享的锁,你可以读,他也可以读在这里插入图片描述
锁一般解决的问题就是资源,抢着使用的时候,就要考虑是否要使用锁在这里插入图片描述在这里插入图片描述在这里插入图片描述
acquire是获得锁,就获得控制权,其他碰到锁,就被阻塞了,一般最简单就是让其他的永久阻塞,如果采用非阻塞和超时的话,一般阻塞是永久阻塞,这时候写代码,阻塞和非阻塞的编程方式是不一样的。
偷懒就是写阻塞
如果是非阻塞行为,就需要判断到底有没有获得锁(获得锁true,没有获得就是false

在这里插入图片描述
有了锁才能release,有一次acquire就需要有一次release,上了锁,没有release,就是死等,死锁就跟多线程无关了
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
这是一个精心设计的例子
在这里插入图片描述
同一个counter实例在10个线程中加加减减在这里插入图片描述
** 在里面做50次减50次加**
在这里插入图片描述
把实例中的一个数,减 了50次,加了50下,预期生成的结果应该是0在这里插入图片描述
现在得出的结果就是0
在这里插入图片描述
现在c1和c2可以调整数值
在这里插入图片描述
c1是调整线程数在这里插入图片描述
c2是在里面调整执行次数在这里插入图片描述
把c2调整了,到达一定数目,结果就出现问题了

按照现在的代码,从上往下执行,,还没执行到最后,上面已经开始计算了。
主线程把10个线程启动了。一直在等在这些none daemon线程
在这里插入图片描述
run这个线程没做完,就可能切换到主线程,打印出来,可能不是0在这里插入图片描述
现在就想看到最终结果是0
第一个循环结束,join结束就再一次循环,只不过这些线程是串行的,对于主线程来讲,做的事情是串行的,做完一件事来下一件事情
在这里插入图片描述
1号线程join,主线程就等1号线程结束,结果2,3线程在等的过程中执行完毕了在这里插入图片描述
多个join写成这样即可
在这里插入图片描述
可以试试底下写死循环
现在这个等于每个event对象等一秒,等于time.sleep(1)

在这里插入图片描述
如果就剩一个主线程,工作线程都完成了,打印一下,
在这里插入图片描述在这里插入图片描述在这里插入图片描述
一般都用容器放起来,再去循环join

但是现在好像还解决不了现在的问题在这里插入图片描述
局部变量是安全,只有一个e是全局的,问题就出在这个上面
在这里插入图片描述
counter是线程不安全管理,在运行的时候被打断,虽然函数看起来是一句,但是转换起来还是有几句代码执行的在这里插入图片描述
右边的操作有可能非常复杂,函数还没处理完,切换到别的线程上去了,就会导致加减的事情没做完在这里插入图片描述
假如刚把self._val的值读出来是1,1+1,还没加完,线程切换,要做的事情是1-1=0,把self._val赋值0
在这里插入图片描述
然后线程切换回来,刚才算1+1=2,直接从0跳到2上
在这里插入图片描述
赋值语句在处理过程中,期望不能被打断在这里插入图片描述
要把它改造程线程安全的在这里插入图片描述
原因就是在于赋值语句可以被断开在这里插入图片描述
就需要加锁,加锁一定要保证用完释放(前面学的with,try finally)
用这种方式
在这里插入图片描述
还有一种方式,with语法,因为里面提供的了enter方法,enter方法里一定提供了acquire,也提供了exit方法,在exit方法,最后一定需要release,就算里面出现了什么异常,最后肯定保证release释放锁。
一般倾向于使用with

在这里插入图片描述
这就把线程安全的变成了线程不安全的在这里插入图片描述
在读的时候最好也加下锁
在这里插入图片描述
现在称counter类是一个线程安全的类在这里插入图片描述
如果自己写的类要多线程来处理的话,就要考虑类是否线程安全,在必要的地方加锁,

所以知道在西面加减的地方,线程可能被打断,要加锁
在这里插入图片描述
两种方法,try finally,和with在这里插入图片描述
能这么使用说明,lock实现了enter(一定acquire)和exit(一定release)魔术方法在这里插入图片描述在这里插入图片描述在这里插入图片描述
这一句不合适
在这里插入图片描述
要改成join或者,下面写的方式在这里插入图片描述在这里插入图片描述

锁适合用于访问和修改同一个资源共享的时候,这时候就适合用锁在这里插入图片描述
一般情况下,锁的原则就是能少用就少用,都是读的适合可能就不需要加锁
在这里插入图片描述

锁的注意事项

在这里插入图片描述
少用锁,只有在资源争抢的时候,大家在读和写的时候,这个时候就需要用到锁,用了锁之后,效率会大大降低(因为很多线程在用到时候,就要等一下,等的越来越多,累计的时间就变长了。

多线程如果用锁,此时相当于一种串行的效果,就不是并行的效果了,如果类似之前的join方式,就变成一种串行的效果,类似高速过省界,都走一个门出去,虽然是并行,但是免不了,在某些场景下,需要使用同一个资源,这样就出现了要不要加锁的问题

但是也可以,很有秩序的使用,是不需要加锁的,加锁是因为,当下多线程谁使用资源不知道,这就是一种争抢现象,争抢行为就需要在适当情况下加锁
加锁的时间越短越好,不用就立即释放掉锁,不释放,别人都在等,程序也就效率低,避免死锁,死锁出现,程序是存在的,但是能干活的线程都死了,死锁还不如崩溃,进程在不在不是健康状况

不使用锁,虽然程序效率高,但是结果是错误的
使用了锁,虽然效率低,但是结果是正确的,能得到健康准确的结果是写程序的目的

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值