条件变量不能进行单独的线程同步
因为不管是条件变量还是锁,都是在阻塞线程,阻塞结束放行
条件变量可以给多个线程放行,多线程访问临界区就会有问题
- 生产者与生产者之间的关系:
互斥,防止将数据放进同一队列中- 消费者与消费者之间的关系:
互斥- 生产者与消费者之间的关系:
同步+互斥
满了不能放,空了不能取
互斥体现:放的时候也不能取
同步概念:
线程以特定顺序访问临界资源
单个生成者消费者同步例子:吃面问题
问题描述:
- val为碗,有面值=1,没有值=0;
- eat线程代表顾客吃面,对val进行- -;make代表厨师做面,对val进行++;
- 正常的屏幕输入输出是0101,也就是顾客吃完厨师才能做面;
- 一个线程不能在执行临界区途中,另一个线程也访问临界区;
使用条件变量
一个线程互斥的访问某个变量,会发现
- 本质是一个PCB等待队列,该等待队列中存放线程或者进程的PCB
- 进入PCB等待队列的线程,只有等待被唤醒才能使用CPU执行后续代码,自身再等待队列中是无法使用CPU的;
- 条件变量必须和互斥锁搭配使用
因为线程1调用wait函数,并进入wait状态是需要时间的,如果在这个过程中,线程2进行唤醒操作已经完成,那么线程1会一直陷入wait状态。
加锁后,线程2必须等到锁释放才能去唤醒。
通过生产者与消费者理解条件变量
现有一个PCB等待队列
- eat线程发现val=0,就调用条件变量的等待接口把自己放入PCB等待队列中;
- make线程执行val=++,就会通知PCB等待队列;
- eat线程就会从PCB等待队列中移除,再执行eat线程;
- val- -,eat线程就会通知make进行
对于make线程也一样
- make线程判断val>=1,就会让自己的线程进入等待队列
同步是让线程产生一定的顺序,之前为什么是所有线程竞争一把锁
以前由系统唤醒线程,现在通过条件变量由程序员自己唤醒线程
条件变量要和互斥锁一起使用
所用接口
线程被唤醒后,pthread_wait函数会自动帮助线程抢锁
加while的原因是防止被伪唤醒,唤醒后要竞争锁,而后续代码直接就往队列里塞数据了(如果队列依然满着就出现问题了)
结论:所有线程同步(你说完我再做就是同步)问题就需要使用条件变量解决
36行的加锁操作通常放在唤醒函数后,因为加锁操作只保证临界资源的原子操作,不过放在唤醒函数后也可以
wait函数参数有互斥锁 && wait的内部逻辑
因为在该函数内部,要先解锁,然后其它线程就可以进行加锁
- eat线程发现碗里没有面,则进入队列中,调用wait函数后将锁释放
- make线程拿到锁,就可以访问碗生产临界资源,并唤醒eat
- eat一旦被唤醒,进行抢锁;
抢到了wait真正返回,回到用户空间执行代码
没抢到wait就阻塞在wait函数内部的抢锁逻辑中,直到抢锁成功才返回
这个锁是第13行加的那把锁
线程是先进入队列再解锁;被唤醒后有一个加锁逻辑,加锁完成后函数才会结束;
假设:线程是先解锁再进入队列,且eat运行速度慢,make快
- 如果eat线程调用wait,先解锁再进入队列,make线程直接加锁,
- make运行快eat运行慢; 可能会导致eat未进入等待队列时,make就以及生成了面,并且通知了一个空的等待队列;
- 此时eat在make通知过后才慢悠悠的进入队列,eat就会一直去等待make通知,这就出现了问题
多生产多消费产生的问题
假设有4个顾客因为没有饭而陷入阻塞
当厨师唤醒时,一下唤醒了3个
三个顾客被唤醒后需要竞争加锁(只有一个会成功)
1号加锁成功后,23号阻塞在锁上,而不是在cond上
使用while和两个条件变量的原因: