首先说明,本文重点不在怎么用条件变量。这里我先列出 apue 中对于pthread_cond_wait函数的这么一段话:
这段话的信息量很大,其中关于互斥量的操作可以理解为以下三个点:
我当时看到这里,各种疑问,传入前为何要锁,传入后为何要释放,返回时又为何再次锁?
本文就这三个问题进行详细解释。不过在此之前,我们需要了解为什么要有条件变量。即条件变量的作用。
如果没有条件变量,那么我们等待一个条件满足则会是下面这样的模型:
首先加锁进入临界区去查看条件是否满足,不满足则解锁离开临界区,睡眠一段时间再继续循环判断。在这种情况下如果刚离开临界区,条件变为满足,那么线程必须还要等一段时间重新进入临界区才能知道条件满足(如果在这段时间内,条件依旧一直保持满足的话),如果这一小段时间条件又变为不满足,那么这个线程还要继续循环判断。不断地加锁解锁(会影响使用同一把锁的其他线程),还不能第一时间收到条件满足。这种模型既费时又开销大。
所以条件变量的产生,正是为了不循环加锁解锁,并且第一时间收到条件满足的通知。
要回答那三个问题,那么首先需要明白 等待与唤醒的配合。
下图是我参考其他人的图(原图有误)更正后所画的。其实这个图就能解释那三个问题:pthread_cond_wait传入前为何要锁,传入后为何先解锁,以及返回前为何再锁。不过我还是详细解释一下。
图中有一个关键点,就是判断条件是否满足,是在调用pthread_cond_wait之前,上锁之后,就是说pthread_cond_wait不具备判断条件的能力,需要我们在外部写判断语句。
可以结合下面的代码会更清楚。
以下pthread_cond_wait和pthread_cond_signal的通常用法的伪代码(条件为:value是不是大于0):
lock(&mutex);
while(value<=0)//需要value>0所以 value<=0就条件不满足
{
pthread_cond_wait(&cond,&mutex);
}
unlock(&mutex);
lock(&mutex);
if(value==0)
{
value++;
}
if(value>0)
{
pthread_cond_signal(&cond);
}
unlock(&mutex);
把这个基本流程弄清楚后,就可以解释那三个问题了。
传入前锁mutex是为了保证线程从条件判断到进入pthread_cond_wait前,条件不被改变。
如果没有传入前的锁。就会有这样的情况:线程A判断条件不满足之后,调用pthread_cond_wait之前,A休眠。线程B更改了条件,使得条件满足,但此时线程A还没有调用pthread_cond_wait。等到线程A又启动调用pthread_cond_wait后虽然条件满足,但却收不到pthread_cond_signal的唤醒,就一直阻塞下去。
传入后的解锁,是因为调用pthread_cond_signal的那部分,需要先加锁更改条件后才调用pthread_cond_signal。(更改条件与等待条件满足,都是针对条件这一个资源的竞争,所以调用pthread_cond_wait和调用pthread_cond_signal的两个线程需要同一把锁)
如果pthread_cond_wait内不对mutex解锁,那么在调用pthread_cond_wait后,其他线程就不能更改条件,条件就会一直不满足。
对于1,这里的理由与传入pthread_cond_wait前锁mutex的理由差不多。如果不锁,那么线程A调用pthread_cond_wait后,条件满足,线程A被唤醒,从pthread_cond_wait返回。线程B在此时更改了条件,使得条件不满足。线程A并不知道条件又被更改,还是以为条件满足,就可能出错。
对于2,只要在pthread_cond_signal之后与解锁mutex之间有其他语句需要执行,那么由于mutex在这时已经被这个线程锁,还没有解锁,所以调用pthread_cond_wait的那个线程在pthread_cond_wait返回前的锁mutex的行为就会阻塞,直到pthread_cond_signal后的语句执行完解锁,pthread_cond_wait才会返回。
说到这里就顺便说一下,由于pthread_cond_wait返回再次锁的行为,pthread_cond_signal不一定放在 lock()和unlock()中间。
lock(&mutex);
//一些操作
pthread_cond_signal(&cond);
//一些操作
unlock(&mutex);
缺点:在某些线程的实现中,会造成等待线程从内核中唤醒(由于cond_signal)回到用户空间,然后pthread_cond_wait返回前需要加锁,但是发现锁没有被释放,又回到内核空间所以一来一回会有性能的问题。
但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。所以Linux中这样用没问题。
lock(&mutex);
//一些操作
unlock(&mutex);
pthread_cond_signal(&cond);
优点:不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了
缺点:如果unlock之后signal之前,发生进程交换,另一个进程(不是等待条件的进程)拿到这把梦寐以求的锁后加锁操作,那么等最终切换到等待条件的线程时锁被别人拿去还没归还,只能继续等待。
条件变量还是应多使用一下,才会有更清晰的理解。只看API还是行不通的。
以上