目录
一、线程同步的概念
1. 饥饿状态
在多线程并发进入一个不可重入的临界资源时,可以使用互斥让线程串行访问该区域,但是互斥并不是每次在任何类似场景都可以使用的,因为互斥是让线程竞争锁,这也就意味着,互斥的线程是谁先拿到票就谁先访问。这就可能导致某个进程或某些进程的竞争力比较高频繁访问资源,使得其他线程抢不到锁而无法访问资源。此时这些线程就处于饥饿状态。
举个例子,假设你的学校里面有一个豪华自习室,里面的设备和环境非常好,并且这个自习室的钥匙就放在门口的一张桌子上,任何人都可以拿着这把钥匙打开自习室进去自习。但是它每次只允许一个人进去自习。
而你是个卷王,每天凌晨4点就起床去这个自习室。当你从宿舍走到自习室时,发现自习室没人,于是你拿着钥匙去打开自习室的门,进去把门反锁然后在里面自习。在你自习的这段时间里,不断有人从宿舍赶到自习室门口,发现里面有人后,就在门口等着,等你离开自习室他好去拿到钥匙进去自习。
当你自习了一个小时后,你想出门上个厕所,你打开门发现外面有很多人都等着,但是没有关系,现在只有你有自习室的钥匙,于是你带着钥匙大摇大摆的去上了个厕所再回自习室。在你上厕所的这段时间里,其他人因为没有钥匙而无法进入,只能看着你上完厕所重新返回自习室。
当你又自习了1个小时后,你想出门去吃个早餐。你打开自习室的门一开,发现外面站着许多人,每个人都看着你,等你放下钥匙。于是你走到桌子面前,刚把钥匙放到桌子上,你转念一想,你4点钟就起床过来占这个位置,才自习了两个小时就走,是不是太亏了。此时你刚放下钥匙,钥匙离你最近,于是你又拿起钥匙起身走回自习室继续自习了,而其他人因为竞争不过你,只能看着你进去。
当你又自习一段时间后,你又从自习室出来,刚把钥匙放下后,又拿起钥匙起身走回了自习室。在接下来的1个多小时里,你都在重复这个行为。于是,在这一个多小时里,其他人就只能看着你时不时的拿着钥匙出来,放下钥匙后又拿起钥匙回去。他们虽然心生不满,但因为竞争不过你而什么都做不了。此时这种其他人看着你反复进入自习室而无法使用自习室的状态,就叫做“饥饿状态”。
2. 同步的概念
以上面的那个例子为例,同步就是学校的自习室管理员发现因为某个同学频繁拿着钥匙出入自习室导致其他人都无法使用自习室的问题,于是出台了一个规定,即当自习室有人使用时,后面来的人如果想使用自习室,都必须排队进入。而进了自习室的同学,只要出了这个自习室,就必须归还钥匙让下一个人进去。如果想再次进去,就需要到后面重新排队。
因此,同步,其实就是在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题。
二、生产者消费者模型
1. 生产者消费者模型基本概念
生产者消费者模型,其实就是使传输数据与使用数据的双方解耦。为什么要解耦呢?举个例子,在我们代码时,会先写一个主函数main,假设在这个主函数中有一个函数func。当main运行到func的时候,main就会将func需要的数据传输给它,func就拿着这些数据去形成变量,进行各种操作。当func在运行的时候,main什么都干不了,只能等待func的结束。这种一方的运行很容易受到另一方运行的影响的关系,就叫做“强耦合”关系。
而生产者消费者模型,就是会提供一个临时保存数据的缓冲区,生产数据的一方将数据填入缓冲区,消费数据的一方从缓冲区拿数据。通过这种方式,让双方不再强依赖对方的生产运行速度,实现双方的解耦。
这样来讲大家可能不太好理解。举个例子。在学校里面,都会有一个超市,假设在这个超市里面有很多火腿肠,而你和你的同学非常喜欢吃火腿肠,于是经常都会去买。那这些火腿肠是由超市生成的吗?并不是,而是由工厂生产的,工厂将生产出来的火腿肠大批量的放入到超市中,以提供需求。
在这个例子里面,学生就是要拿取数据的多个线程,工厂就是要生产数据的多个线程;而超市,则是一个缓冲区,用于为双方提供一个存放和拿取数据的区域。
2. 生产者、消费者之间的关系
2.1 消费者与消费者的关系
还是上面的例子,假设有一天,超市里面的火腿肠卖完了,但是你不知道,于是你走到超市里面,发现货架上已经没有火腿肠了,但是刚好有一个工作人员在向上面放火腿肠。此时又来了一个同学,也发现货架上没有火腿肠,于是他也走过来。此时你和他就同时看到了一份火腿肠,都想将它拿走,你和他之间就处于竞争关系。要避免这种情况,就不能让多个消费者同时进到超市的火腿肠货架上看到同一个火腿肠。
因此,消费者和消费者之间的关系,就是“互斥关系”。
2.2 生产者和生产者的关系
在为超市提供货源的工厂中,每个工厂都生成不同品牌的火腿,一个生成金华火腿,一个生产双汇火腿。虽然它们都生成火腿,这两个工厂都想将自己的火腿摆进去。但超市的货架并不大,只能摆下一种火腿。此时这两个工厂直接就要竞争这个货架,决定放谁的火腿肠。此时,它们之间就处于竞争关系。要避免这个问题,就不能让几个工厂同时向超市放火腿肠。
因此,生产者和生产者之间的关系,也是“互斥关系”。
2.3 生产者和消费者的关系
依然是上面的例子。假设有一天,你在去超市买火腿肠的时候,正好工厂的工作人员正在往货架上摆火腿肠。当你走到货架上刚准备拿一个火腿肠的时候,工作人员走过来把你要拿的金华火腿肠换成了双汇火腿肠。但你因为正在玩手机,没看到这一行为,于是你拿起火腿肠走过去结账,等到了宿舍要吃的时候才发现你买的火腿肠的牌子不对。但你此时已经拆开了火腿肠,没办法退货,只能自认倒霉。这就是因为消费者和生产者同时访问超市导致的。
这就好像消费者线程在去缓冲区拿数据时,生产者线程也在传输数据,此时消费者线程就可能拿到错误的数据并带回去使用。因此,生成者和消费者的关系首先是“互斥关系”。
假设某一天,超市里面的火腿肠卖完了,但是你并不知道,于是你走到超市里面,看到货架上没有火腿肠后,就跑去询问超市的工作人员火腿肠什么时候才到。超市人员说工厂放假了,他也不知道,于是你就回去了。等到了第二天,你又跑去问,超市的人还是回答不知道。就这样过了一个月,每天你都会跑去询问工作人员火腿肠到没到。
在这个过程中,即浪费 了你的时间,也浪费了超市的时间。因为你每天去问一趟,路上要花费时间,而超市的工作人员每次回到这个问题,也要花费时间,假设一天里有1000个人在问这个问题,超市的工作人员就要回答1000次,浪费了大量时间。于是超市的工作人员就想了个办法,他加了来询问火腿肠的人的微信,然后告诉你们,当火腿肠到货时,他就通过微信告诉你们,你们就不用每天都过来问了。
而工厂那边在结束放假后,就开始马不停蹄的进行生产。每生产出一批火腿肠后,工厂的人就去问超市的人超市的货架还放不放得下,几乎每天如此。超市的工作人员就感到很烦躁,于是就加了工厂的人的微信,告诉他们,当超市需要火腿肠时,就在微信上告诉你们,你们把货运过来就行,别时不时的过来问了。
此时,超市的工作人员就同时拥有了消费者和生产者的联系方式,每当有货或缺货的时候就微信上告诉对方,无需对方时不时的过来询问。在这个工程中,就实现了消费者和生产者的“同步”。
因此,消费者和生产者之间的关系就是既要互斥,也要同步,即“互斥与同步关系”。
3. “321”原则
生产者和消费者模型,总结起来就是要遵循“321”原则。
3,指的是三种关系——消费者与消费者(互斥)、生产者与生产者(互斥)、消费者与生产者(互斥(保证共享资源安全)与同步)
2,指的是两个角色——消费者线程与生产者线程<