说到条件变量,首先说下互斥锁,互斥锁是最一种同步形式,用于保护临界区,以保证任何时刻只有一个线程在执行其中的代码(假设互斥锁由多个线程共享),来保证共享数据的完整性,上锁过程如下图;
假如在一个程序中由3个线程访问一个共享变量g_Count,其中线程1和线程是负责对g_Count变量加一,线程3是负责对g_Count变量减一;线程4是负责判断g_Count是否大于等于100,是就将变量g_Count清零,代码片段如下:
//线程1,2while (1){ pthread_mutex_lock(&mutex); g_Count++; pthread_mutex_unlock(&mutex); sleep(1);}
//线程3while (1){ pthread_mutex_lock(&mutex); g_Count--; pthread_mutex_unlock(&mutex); sleep(1);}
//线程3while(1){ pthead_mutex_lock(&mutex); if (100 >= g_Count) { printf("g_Count >= 100\r\n"); g_Count = 0; pthread_mutex_unlock(&mutex); } else { pthread_mutex_unlock(&mutex); } sleep(1);}
上面这段代码中的线程4并不知变量g_Count什么时候才会大于等于100,这就需要一直的循环判断,但是每次的判断都有上锁和解锁的操作(上锁和操作是很费时的),这会带来CPU资源浪费;对于这个问题可以使用条件变量处理;
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号);为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;
条件变量是由互斥锁保护的,线程在改变条件状态前必须先锁住互斥锁,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥锁才能计算条件;
将之前的代码使用条件变量修改:
//线程1,2while (1){ pthread_mutex_lock(&mutex); g_Count++; pthread_mutex_unlock(&mutex); if (g_Count >= 100) { pthead_cond_signal(&g_cond); } sleep(1);}//线程1,2while (1){ pthread_mutex_lock(&mutex); g_Count--; pthread_mutex_unlock(&mutex); sleep(1);}//线程4while(1){ pthead_mutex_lock(&mutex); while (g_Count < 100) { pthead_cond_wait(&g_cond, &mutex); } g_Count = 0; pthread_mutex_unlock(&mutex); sleep(1);}
在《UNIX环境高级编程》中,有这么一段:
;所以上面这段代码中的线程3如果g_Count小于100,线程3会调用pthread_cond_wait,而pthread_cond_wait会释放mutex,然后等待条件变为真返回,pthread_cond_wait返回时会再次锁住mutex;条件不满足时pthread_cond_wait会等待,从而不用一直的轮询,减少CPU的浪费;
至于上面的代码中的使用while的原因,在多核处理器下,pthread_cond_signal可能会激活多于一个线程(阻塞在条件变量上的线程);结果就是,当一个线程调用pthread_cond_signal()后,多个调用pthread_cond_wait()或pthread_cond_timedwait()的线程返回;这种效应就称为“虚假唤醒”;
在pthread_cond_wait的man手册中,对虚假唤醒有这样一段话:
需要对条件进行再判断以避免虚假唤醒:
如果用if判断,多个等待线程在满足if条件时都会被唤醒(虚假的),但实际上条件并不满足,生产者生产出来的消费品已经被第一个线程消费了;
这就是我们使用while去做判断而不是使用if的原因:因为等待在条件变量上的线程被唤醒有可能不是因为条件满足而是由于虚假唤醒;所以我们需要对条件变量的状态进行不断检查直到其满足条件,不仅要在pthread_cond_wait前检查条件是否成立,在pthread_cond_wait之后也要检查;
参考: