简单介绍
引入 pthread_cond_wait
函数的概念,它是 POSIX 线程库中用于条件变量等待的函数。一下是此函数运行时内部逻辑:
1. pthread_cond_wait 函数的调用过程
描述当程序调用 pthread_cond_wait
函数时会发生什么。包括将执行流放入 PCB 等待队列、解锁等待。
2. 等待被唤醒
解释在等待被唤醒期间会发生的事情,以及当条件变量满足时,如何唤醒等待线程。
3. 从等待队列中移除
说明当线程被唤醒后,操作系统如何从 PCB 等待队列中移除线程。
4. 抢占互斥锁
详细介绍线程在等待结束后,如何尝试抢占互斥锁。
5. 抢占互斥锁的阻塞逻辑
解释当线程无法立即获得互斥锁时,会发生什么。包括线程被切换出 CPU、上下文信息的保存等。
6. 重新抢占互斥锁
描述当线程再次获得 CPU 资源后,如何继续抢占互斥锁。
7. 等待结束
说明当线程最终获得互斥锁时,pthread_cond_wait
函数如何返回。
详解
条件变量的等待
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict conpthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
为什么这两个接口中有互斥锁?
条件不会无缘无故地突然变得满足了, 必然会牵扯到共享数据的变化。所以一定要有互斥锁来保护。没有互斥锁, 就无法安全地获取和修改共享数据。
同步并没有保证互斥,而保证互斥是使用到了互斥锁。
pthread_mutex_lock(&m)
while(condition_is_false)
{
pthread_mutex_unlock(&m);
//解锁之后, 等待之前, 可能条件已经满足, 信号已经发出, 但是该信号可能会被错过
cond_wait(&cv);
pthread_mutex_lock(&m);
}
上面的解锁和等待不是原子操作。解锁以后, 调用cond_wait之前,如果已经有其他线程获取到了互斥量, 并且满足了条件, 同时发出了通知信号, 那么cond_wait将错过这个信号, 可能会导致线程永远处于阻塞状态。所以解锁加等待必须是一个原子性的操作, 以确保已经注册到事件的等待队列之前, 不会有其他线程可以获得互斥量。
那先注册等待事件, 后释放锁不行吗?注意, 条件等待是个阻塞型的接口, 不单单是注册在事件的等待队列上, 线程也会因此阻塞于此, 从而导致互斥量无法释放, 其他线程获取不到互斥量, 也就无法通过改变共享数据使等待的条件得到满足, 因此这就造成了死锁。
pthread_mutex_lock(&m);
while(condition_is_false)
pthread_cond_wait(&v,&m);//此处会阻塞
/*如果代码运行到此处, 则表示我们等待的条件已经满足了,
*并且在此持有了互斥量
*/
/*在满足条件的情况下, 做你想做的事情。
*/
pthread_mutex_unlock(&m);
pthread_cond_wait函数只能由拥有互斥量的线程来调用, 当该函数返回的时候, 系统会确保该线程再次持有互斥量, 所以这个接口容易给人一种误解, 就是该线程一直在持有互斥量。事实上并不是这样的。这个接口向系统声明了我在PCB等待序列中之后, 就把互斥量给释放了。这样其他线程就有机会持有互斥量,操作共享数据, 触发变化, 使线程等待的条件得到满足。
pthread_cond_wait内部会进行解锁逻辑,则一定要先放到PCB等待序列中,再进行解锁。
while(condition_is_false)
pthread_cond_wait(&v,&m);//此处会阻塞
if(condition_is_false)
pthread_cond_wait(&v,&m);//此处会阻塞
唤醒以后, 再次检查条件是否满足, 是不是多此一举?
因为唤醒中存在虚假唤醒(spurious wakeup) , 换言之,条件尚未满足, pthread_cond_wait就返了。在一些实现中, 即使没有其他线程向条件变量发送信号, 等待此条件变量的线程也有可能会醒来。
条件满足了发送信号, 但等到调用pthread_cond_wait的线程得到CPU资源时, 条件又再次不满足了。好在无论是哪种情况, 醒来之后再次测试条件是否满足就可以解决虚假等待的问题。
pthread_cond_wait内部实现逻辑:
-
将调用pthread_cond_wait函数的执行流放入到PCB等待队列当中
-
解锁
-
等待被唤醒
-
被唤醒之后:
1、从PCB等待队列中移除出来
2、抢占互斥锁
情况1:拿到互斥锁,pthread_cond_wait就返回了
情况2:没有拿到互斥锁,阻塞在pthread_cond_wait内部抢锁的逻辑中
当阻塞在pthread_cond_wait函数抢锁逻辑中时,一旦执行流时间耗尽,意味着线程就被切换出来了,程序计数器就保存的是抢锁的指令,上下文信息保存的就是寄存器的值
当再次拥有CPU资源后,恢复抢锁逻辑
直到抢锁成功,pthread_cond_wait函数才会返回