2019/12/25 04-barrier、semaphore和GIL

在这里插入图片描述
信号量用处很大,但是常用的就是event和lock,信号量在有些地方必须使用,信号量跟锁有些像,锁lock(用了一个再要就不行了,所获取不到了,因为用了一次就需要release一次,没有release,就获取不到,要么等会,要么就一直等,)
信号量内部会维护一个倒计数器,每一次获取这个锁的时候,获取一次,往下减1,当acquire方法,再次调用的时候,会发现计数为0,就阻塞请求的线程

当有线程还回来,刚才请求(阻塞的)就可以获得一次了,计数大于0,恢复阻塞的线程在这里插入图片描述在这里插入图片描述
semaphore构造方法默认一个,代表acquire一下就没了,value小于0直接抛出异常 在这里插入图片描述
在这里插入图片描述
计数器等于0的时候,其他线程访问,如果acquire,阻塞的就是你被阻塞的时间,none就是永久阻塞
在这里插入图片描述在这里插入图片描述在这里插入图片描述
倒计数从3开始在这里插入图片描述
condition和value,下面有acquire,说明有上下文可以用在这里插入图片描述
这个倒计数是,每请求,往下减在这里插入图片描述在这里插入图片描述
再请求,就阻塞了,timeout=3,阻塞三秒超时在这里插入图片描述
只有RLock才跟线程相关,其他是,所有线程共用一个对象,lock,event也是如此

现在用完了,就可以来归还了
在这里插入图片描述
在另一个线程内获取了这把锁在这里插入图片描述在这里插入图片描述
不管跨不跨线程,同一个信号量对象,只要问他获取一次,只要获取成功,值就会往下-1,直到为0为止,减到0还有线程,不管在哪个线程,只要对同一个信号对象,再调用acquire,就会阻塞,直到有一个线程,release,才算恢复,恢复了,才能去尝试获取信号量,获取到了,就可以执行,没获取到就继续等

会有一个超界的现象
在这里插入图片描述
如果是6,下面可以获取多少次,本来是限定使用三下,结果,通过多次release,相当于想要多少次就多少次,但是这样就破坏了初始化过程,初始化就是限制次数,一多release,这个次数就控制不住了
在这里插入图片描述在这里插入图片描述
这样就只需做一件事情,boundesemaphore,有边界的semaphore
在这里插入图片描述
表示给太多了,不允许在这里插入图片描述
手动release,忘了次数,就可以用with语法,但是with不可以跨函数,就要考虑如何去release,release多了就要考虑是否用有界的倒计数boundesemaphore来处理在这里插入图片描述在这里插入图片描述
这一句去掉就没有初始给的值了
在这里插入图片描述
有界的就是加了初始值是多少,比较当你归还的数字超过了初始给的,告诉你就是多还了,所以是多加了个属性来判断的,一个condition,一个value在这里插入图片描述在这里插入图片描述
当出现多还的情况,就用有界的semaphore来解决问题,如果多还,会抛出异常
在这里插入图片描述

信号量如何做,
应用,资源是有限的,要去打开链接,每一次建立链接的成本很高(一般不推荐平凡断开链接,和重新链接)
可以给一个链接池,这就是个存放链接的容器,最简单的就是列表。
这样就让大家找一找有没有空闲的列表,但是因为资源和带宽有限,不可能无限的创建(1000个请求,创建1000个链接,可以一个用完,不断开,让别人来使用)
有 三座桥,别人用的时候,其他就无法使用,最多同时有三个人来链接桥,第四个人只能等待,剩下来的只能等待,其中有座桥release,空闲了,等待中的某一个人就坐上桥了,
用一个减一个,当为0的,再来访问就不行了

资源有限,(讲锁的时候,lock,资源唯一),现在资源有三个,资源是有限个,当资源被用完之后,就不能再去开辟资源 了,资源有限不可能无限制服务,
所以当只有一个资源就用lock,三个资源就需要信号量

信号量跟锁类似,都是解决资源有限的问题,锁一般是唯一资源,上锁不能使用

在这里插入图片描述
模拟连接池,这个代码是有瑕疵的,只是实验
在这里插入图片描述
要抽象类,给链接类,和连接池类,池是容器类,里面方链接,在这个池对象内,放链接

在池内,有大小,是有限的,所以写代码的时候要处处节约资源,应该尽可能段的占用资源,用完就释放掉,但是链接资源有特殊的地方,只要是建立网络链接的时候,太耗时了,还不如创建好,大家一起利用,剩余的就得等,用完就空闲,让别人用
在这里插入图片描述
为什么用池就是因为需要平凡创建这个资源,每一次创建,消耗的内存和时间是非常大的,创建好之后就不要销毁了,反复利用,但不是共享利用,反复利用是独占的,你占了,我就不能用了

连接池和线程池是比较常见的,线程虽然是轻量,创建也是有代价的,就用多线程,重复利用线程

创建10个链接,把10个链接对象,放在self.pool的属性上,让别人使用在这里插入图片描述
这是一个列表
在这里插入图片描述
有了池就不创建链接了,

这句话有大问题,多线程的时候不能保证这两句是一起执行的,一个线程正准备拿,就被切换了让别的线程拿走了,原本的线程就拿不到了
在这里插入图片描述
有可能多个线程都在调用这个函数,都想从线程池里拿出来,自己用,应该加lock
在这里插入图片描述
这一块其实用信号量比较好,不用lock,安全用有界的信号量
在这里插入图片描述
假设就count3个资源,3个被拿走了,如果第4个来拿,就永久阻塞了
在这里插入图片描述
release还回来在这里插入图片描述
返回的的conn什么类型就需要判断,比如弄ID验证,还的对象必须有ID,类型对不对,ID对不对在这里插入图片描述
应该是还完之后,才把信号量归还上去
在这里插入图片描述
抢的时候,先抢信号量,还的时候,先还资源,后归还信号量,
在这里插入图片描述
如果使用的时候,资源是单一的,又想独占,这时候建议使用lock,如果资源是有限个,只要没人归还,就不能用,就使用信号量

跨函数不能用with在这里插入图片描述
get_conn,是有线程安全的,如果在多线程用,记得,几乎所有的语句都是可以打断的(甚至+=1都可以打断)
在这里插入图片描述在这里插入图片描述
这句话太不安全
在这里插入图片描述
可以加锁,或者使用信号量
在这里插入图片描述在这里插入图片描述在这里插入图片描述
递减的方式使用信号量,
在这里插入图片描述
要注意谁前谁后
在这里插入图片描述在这里插入图片描述
下面是得到链接来模拟使用时间,每个线程拿到链接,使用的秒数是不一致的,用完之后就归还,在这里插入图片描述
如果池中有资源,请求者获取资源时信号量减1,拿走资源。当请求超过资源数,请求者只能等待。但使用者用完归还资源后信号量加1,等待线程就可以被唤醒拿走资源。在这里插入图片描述在这里插入图片描述
return_conn方法可以单独执行,有可能多归还链接,也就是会多release,所以要有界信号量BoundedSemphore
在这里插入图片描述在这里插入图片描述
多线程,多release就会出现问题在这里插入图片描述
同一个链接归还多次,也是要判断的,在生产环境就要判断别人还的是什么在这里插入图片描述
正常情况下都会先获取信号量,用完之后归还。
创建很多个线程,都去获取信号量,没有获得信号量的线程都会阻塞。(保证拿到资源允许,没拿到资源阻塞)
能归还的线程都是前面获取到信号量的线程,其他没有获得线程都阻塞着。非阻塞的线程append后才release,这时候等待的线程被唤醒,才能pop,也就是没有获取信号量就不能pop,这是安全的。

信号量比计算列表长度好,线程安全,一旦多线程,线程安全是必须要的在这里插入图片描述
锁,只允许同一个时间一个线程独占资源。是特殊的信号量,因为它的信号量计数器可以理解为初始值1。
信号量,可以多个线程访问这个资源,但这个资源数量有限,其他访问的只能等释放掉,
锁,可以看做特殊的信号量。

在这里插入图片描述
并不是所有的锁的用完就释放,这样就可以用with,但是有些是在一个函数使用锁,在另一个函数解开,所以就需要保证用过就释放

数据结构

在这里插入图片描述
在queue模块里有先进先出,FIFO的queue,queue类是线程安全的,适用于多线程间安全的交换数据。内部使用了Lock和Condition,用这两样东西来保证数据处理是安全的

但是实际有queue.qsize,当你读这个数据为0,get的时候不保证你一定能拿到这个数据。其实就是告诉你,if qsize >0 下面做操作(if语句是可以被打断的,防止打断,只能加锁)在这里插入图片描述
所以因为这个if的原因,如果以qsize作为依据的话在这里插入图片描述
类似下面这种情况,都有可能被打断,到底能不能put塞进去,还是get出来在这里插入图片描述在这里插入图片描述
读下queue的代码,condition里有个锁,称为mutex,一般称为互斥量,在这里插入图片描述
这些都是condition,比如所有任务完成,在这里插入图片描述
里面做了大量的判断
在这里插入图片描述
使用with语法是因为,not_empty是个condition,是个互斥量,就是一把锁
在这里插入图片描述在这里插入图片描述
用这些技术就可以保证线程安全的在这里插入图片描述
如果在多线程的时候,如果要使用数据,要想让多线程安全,这时候就需要去用队列用queue,但是queue有两个,
一个是多进程用的,一个是多线程用的
在这里插入图片描述
多线程 用queue.queue
在这里插入图片描述

面试经常问 GIL全局解释器锁

在这里插入图片描述
在进程级别的全局的锁
cpython中在解释器进程级别有一把锁,叫做GIL全局解释器锁。
GIL保证在Cpython进程,只有一个线程在执行字节码,即时是在多核的情况下,这把大锁,保证在同一时间,只能在某一个CPU核心上,当前进程的一个线程在执行
cpython中所谓的并行其实是假象。
cpython有了这个锁,就可以要求,线程调度在不同的cpu上,当下一个核心只能有一个线程在跑,真正干活的只有一个线程,其他的就是被迫等待,等待全局解释器锁,释放了另一个cpu里的,当前程序的线程

在这里插入图片描述
正好启动4个线程,如果调度4个线程,把4个线程分配到cpu核心上,自以为并行处理,在同一进程内的4个线程,分配到 了4个cpu核心上,并行处理是假象,因为有了这把GIL锁,在一个时刻,只能在某一个cpu运行一个线程,其他锁住了就被迫等待,但是因为切换很快,因为线程分时间片,由它来控制在某个cpu上运行在这里插入图片描述
如果一个cpu也是并行,因为串行是一号执行完才是2号,但是这里就分时间片,交替运行,就是假并行 了,线程是交替并行在跑,给人的感觉是并行,有GIL解释器大锁,即时有多行,想要调度到多核上并行起来,因为有这个GIL大锁,也是做不到的,这是python被人诟病的地方(这个是多线程的问题,多进程其实就可以解决了,原来一个进程,可以开三个进程)在这里插入图片描述
cpython中,IO密集,线程阻塞,就会调度其他线程
这个GIL大锁有特点,当你执行语句,到IO的时候(等待,IO完成才变成就绪)大量时间等IO,是浪费的,想快都快不了,这个时候就比较适合用python的多线程,

但是如果是cpu密集型,如果有需要大量计算cpu,想要几个核心上的线程一起,但是有GIL锁,在某个时刻在某一个核心上只能有一个线程在跑,其他核心上 线程就不能跑,这样大量时间就浪费了,这个时候就不太合适了
这样就会有一个现象,一个时刻只能在某个核心上只能一个线程在跑,其他核心上想并行的线程只能在等待,浪费时间。
还有一个现象,因为要唤醒那些线程,也需要时间,而且是抢GIL这把锁的,就会有某一个线程会频繁获得这个锁,其他线程一直在睡觉,因为唤醒线程需要时间,但是锁已经开始抢了,趁别人没醒,就又抢到了

如果cpu密集型,在cpython使用就会出现这样的情况

在这里插入图片描述
cpu密集型,使用多进程,不要使用多线程,绕开GIL在这里插入图片描述
如果真的在意多线程问题,就用erlang和go语言
在这里插入图片描述

python中绝大多数内置数据结构的读、写操作都是原子操作。也就是不会出现读半截,写半截的问题,在这一点上看似线程安全的,但是这个数据不能说是线程安全的。
由于GIL的存在,python的内置数据类型,在多线程编程的时候就变成安全的了(因为读写不可拆分,在每个时刻,只有一个CPU核心在使用这个数据结构的对象),但是实际上它们本身不是线程安全类型(线程安全目前只有queue,因为有lock存在,内建数据类型其实不是线程安全带,因为在同一时刻只有一个核心在使用数据,还保证读写是必须完成的,不可分割)
在这里插入图片描述
在单线程使用GIL,效率是很高的,其实python鼓励你单线程,因为有携程,携程效率很高
在这里插入图片描述
下面有两个程序
下面的程序是cpu密集型的

在这里插入图片描述
不要写print,print要IO,线程就会被休息,到就绪的时候
在这里插入图片描述在这里插入图片描述
运行起来
在这里插入图片描述
现在假设是只有主线程,执行完一个,执行下一个,把5个执行完在这里插入图片描述
这是典型的多线程,cpu密集型,使用多线程来跑(本来想的是并行,但是实际是一个做完了下一个来做)
使用GIL锁之后,就算cpu密集型调度到不同核心上去,只能保证在某一个时刻某个核心上,会执行进程的某个线程,相当于分散到多个核心执行,但是还是一个单线程来执行
在这里插入图片描述
如果有IO语句,就会影响计算,从两个程序测试来看,cpython中多线程根本没有任何优势,和一个线程执行时间相当。因为GIL的存在,尤其是像上面的计算密集型程序,就不要使用python多线程做了。
在这里插入图片描述
第一段代码跑完,232秒在这里插入图片描述
跑下一段代码
在这里插入图片描述
主线程一个,再开5个线程,其他辅助线程,加起来是7个在这里插入图片描述在这里插入图片描述
跑出来的结果应该和上面的单线程差不多

python中多线程的技术,event,lock,Rlock,condition,barrier,semaphore,
event是看状态变化,但是可以很多都看这个状态变化(event可以代替time.sleep,也可以通知其他人类似conditinon)
condition是通过notify方法来通知等待的,event是状态变化不通知你,你自己看。
对于锁来讲,最常用的是Lock,LOck是semaphore都是解决资源争用的问题,lock只有一个资源,大家都要抢,对于semaphore来讲,多个资源,大家一起抢,但是资源有限,抢完没有。这些用完都需要归还,归还就有空闲,空闲了就可以让别人使用。
唯一的用锁即可,多个资源用semaphore。
Rlock是可重入锁,递归锁,某一个线程获得锁之后的属主属于它,就可以重复进入,进几回release几次,不release,count无法清0,不清0,其他线程用不了,是线程相关的锁

barrier,我们做事情必须达到一个条件,谁做完了谁等,所有的做完了,才能做下一步,等参与方targets齐了,下一步,尤其是程序运行的时候可能需要(加载配置,建立连接)把这些初始服务做好了,程序才能起来。

semaphore的release有点问题,所以加边界boundedsemaphore,线程同步技术和多线程息息相关在这里插入图片描述
谁快谁慢不好说,因为太接近了,运行了10亿次,运行5个,等于50亿次,这种时间是墙上时间,不是cpu真正分的时间,刚才用多线程的cpu密集型代码,相当于单线程的串行执行
在这里插入图片描述
因为GIL会导致CPU密集的时候,同一时刻,只能有一个线程在执行,而且只能在某一个核心上在这里插入图片描述
多线程是操作系统提供的,要用,只不过python加了一把GIL锁,其他语言不一定有,所以使用方法上有区别。有GIL的存在,内置的数据结构就变的安全了
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值