线程同步:为什么条件变量要和互斥锁一起使用,而信号量不用?
条件变量需要搭配互斥锁使用的原因
从调用pthread_cond_wait()到真正被挂起进入等待队列需要时间)
为了应对线程1在调用pthread_cond_wait()但线程1还没有进入wait cond的状态的时候(),此时如果线程2调用了 cond_singal 的情况,那么这个cond_singal就丢失了,有可能造成线程1无限等待。
加了锁(wait与singal用同一把锁)的情况是,线程2必须等到 mutex 被释放(也就是 pthread_cod_wait() 真正进入wait_cond状态并释放锁,此时线程2才能获得锁并调用cond_singal,这样就会保证线程2顺利进入wait状态而且线程2的cond_singal信号不会丢失;
mutex+条件变量错误使用
mutex+条件变量的错误示例:(还是有可能造成信号丢失)
mutex需配合pthread_cond_wait()函数正确使用;
pthread_cond_wait(cond,mutex);保证了该线程先进入cond_wait状态,再释放锁是一个线程安全的原子操作;
虚假唤醒
这里判断条件设置成while()的原因是因为Linux多线程中存在虚假唤醒(由于某种原因某个cond_singal有可能唤醒了多个wait的进程),则用while()会反复判断当前条件,就算被bug误唤醒,但是条件成立还是会进入wait状态;
模拟虚假唤醒的情况(多生产多消费模型):
- 假设初始队列为空, 此时thread2阻塞在wait中:
- thread3生产一个, 此时队列不为空, thread3发送signal通知, 并且unlock;
- thead2的wait虽然收到通知了, 但是thread2和thread1还在竞争lock(线程2,1都是消费者,signal后都从wait队列进入锁竞争队列)
- 假设thread1先抢到了lock, 消费了, 此时队列为空, 然后unlock;
- 此时假设thread2抢到了lock, wait也就终于返回了, 但是有while循环, 再次判断队列是否为空, 发现仍然为空(已经被thread1偷过去消费掉了), 所以并不能退出while循环, 所以再次wait, 这就正常了
信号量不需要配合mutex使用的原因
信号量的本质是一个计数器
借用环形生产消费队列模型举例:
sem_t sem_data;//生产的产品数量;
sem_t sem_space;//剩余空间数量;
等待信号量
功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()操作
发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()操作
当需要取走产品时,会先看看sem_data里-1以后会不会<0,如果其<0则wait,否则继续执行;
取走成功以后会给sem_post(space_sem),相当于剩余空间+1;
同时,当生产产品的时候会先检测有没有剩余空间,如果space_sem-1<0(它==0)则会进入wait状态,等待某次去走后space_sem+1,再继续生产;
所以信号量是利用多线程改变计数器数量动态实现同步的,同时P / V操作是原子操作,因此不需要搭配互斥锁使用;
总结
条件变量+互斥锁 和 信号量 都是线程同步的方法,适用于不同的场景;