在多线程同步互斥的应用场景下,通常会用到pthread_cond_wait()和pthread_cond_signal()函数。那么这两个函数到底是如何保证互斥同步的呢?


为了对上面的问题有个直观的了解,可以从下面的问题着手。下面两段这些程序有什么bug么?


a. 等待条件的线程执行的代码:

Thread 1:

{

……………………..

pthread_cond_wait(cv, mutex);


do_worker_task();

……………………..

}

b.唤醒等待线程的代码:

{

pthread_mutex_lock(mutx);

pthread_cond_signal(cv)

pthread_mutex_unlock(mutex);

}


要知道上面代码问题在哪,关键是必须对pthread_cond_wait()有深入的了解。为了对这个函数又直观的了解,下面给出了pthread_cond_wait(cv, mutex)正常执行过程中的几个重要子步骤伪码:


pthread_cond_wait(cv, mutex) 实际做的事情:

a. pthread_mutex_unlock(mutex);

b. do_real_cond_wait(cv); // 可能在这阻塞直到其他线程调用pthread_cond_signal(cv)或者  pthread_cond_broadcast(cv),才会往下执行

c. pthread_mutex_lock(mutex);


结合在多处理器、多线程、高并发的环境下,上面三个步骤就显得缺一不可,才能保证尽可能地互斥:

步骤a:保证其他线程此后也有等待、申请条件变量的同等机会

步骤b:在条件变量还没就绪的情况下,执行真正的阻塞

步骤c:当前线程得到条件变量之后,阻止其他等待条件变量的线程继续往下执行,而自己可以往下执行。


那么问题来了,考虑步骤c,如果单独一句pthread_cond_wait(cv, mutex)是无法确保线程尽可能互斥的,这就是为何在执行pthread_cond_wait()之前要求有pthread_mutex_lock(mutx)的原因了,因此正常申请条件变量的线程的流程应该如下:


Thread 1:

{

pthread_mutex_lock(mutx);


pthread_cond_wait(cv, mutex);


do_worker_task();


pthread_mutex_unlock(mutex);


}

至于释放条件变量,理解了上边的内部机制之后,其实在释放条件变量之前就不需要申请锁再释放锁了。


这里要特别提到的一点是,按照上面正确申请条件变量的的流程,仍然存在多个等待条件变量的线程同时执行步骤b出等待,此时如果有信号释放,就会唤醒多个线程。这也就是为什么手册上说pthread_cond_signal(cv)至少唤醒一个在等待cv的线程(可以计数资源为1)的原因了。


参考文档和手册:

man pthread_cond_signal