一、条件变量定义
有的时候仅仅依靠锁住共享资源来使用它是不够的。有时候共享资源只有某些状态的时候才能够使用。比方说,某个线程如果要从堆栈中读取数据,那么如果栈中没有数据就必须等待数据被压栈。这种情况下的同步使用互斥锁是不够的。另一种同步的方式--条件变量,就可以使用在这种情况下。条件变量(Condition Variable)是线程间的一种同步机制,提供给两个线程协同完成任务的一种方法,使用条件变量可以以原子方式阻塞线程,直到某个特定条件为真为止。条件变量的测试一般是用互斥量来保护的,用来确保每一时刻只有一个线程能够改变条件变量,如果条件为假,线程通常会基于条件变量而阻塞,并以原子方式释放等待条件变化的互斥锁。如果另一个线程更改了条件,该线程可能会向相关的条件变量发出信号,从而使一个或多个等待的线程执行以下操作:
- 唤醒
- 再次获取互斥锁
- 重新评估条件
在以下情况下,条件变量可用于在进程之间同步线程:
- 线程是在可以写入的内存中分配的
- 内存由协作进程共享
Condition Variable用pthread_cond_t类型的变量表示,和Mutex的初始化和销毁类似,pthread_cond_init函数初始化一个Condition Variable,attr参数为NULL则表示缺省属性,pthread_cond_destroy函数销毁一个Condition Variable。如果ConditionVariable是静态分配的,也可以用宏定义PTHEAD_COND_INITIALIZER初始化,相当于用pthread_cond_init函数初始化并且attr参数为NULL。
条件变量的相关函数如下:
#include <pthread.h> int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr); int pthread_cond_signal(pthread_cond_t *cptr); //Both return: 0 if OK, positive Exxx value on error
pthread_cond_wait用于等待某个特定的条件为真,一个线程可以调用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数做以下三步操作:
1. 释放Mutex
2. 阻塞等待
3. 当被唤醒时,重新获得Mutex并返回
注意:3个操作是原子性的操作,之所以一开始要释放Mutex,是因为需要让其他线程进入临界区去更改条件,或者也有其他线程需要进入临界区等待条件。
pthread_cond_signal用于通知阻塞的线程某个特定的条件为真了。在调用者两个函数之前需要声明一个pthread_cond_t类型的变量,用于这两个函数的参数。
为什么条件变量始终与互斥锁一起使用,对条件的测试是在互斥锁(互斥)的保护下进行的呢?因为“某个特性条件”通常是在多个线程之间共享的某个变量。互斥锁允许这个变量可以在不同的线程中设置和检测。
通常,pthread_cond_wait只是唤醒等待某个条件变量的一个线程。如果需要唤醒所有等待某个条件变量的线程,需要调用:
int pthread_cond_broadcast (pthread_cond_t * cptr);
默认情况下面,阻塞的线程会一直等待,知道某个条件变量为真。如果想设置最大的阻塞时间可以调用:
int pthread_cond_timedwait (pthread_cond_t * cptr, pthread_mutex_t *mptr, const struct timespec *abstime);
如果时间到了,条件变量还没有为真,仍然返回,返回值为ETIME。
二、条件变量使用规范
(一)、等待条件代码
pthread_mutex_lock(&mutex);
while (条件为假)
pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);
(二)、给条件发送通知代码
pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
至于为什么在被唤醒之后还要再次进行条件判断(即为什么要使用while循环来判断条件),是因为可能有“惊群效应”。有人觉得此处既然是被唤醒的,肯定是满足条件了,其实不然。如果是多个线程都在等待这个条件,而同时只能有一个线程进行处理,此时就必须要再次条件判断,以使只有一个线程进入临界区处理。考虑以下情况:
1,pthread_cond_signal在多处理器上可能同时唤醒多个线程,当你只能让一个线程处理某个任务时,其它被唤醒的线程就需要继续 wait,while循环的意义就体现在这里了,而且规范要求pthread_cond_signal至少唤醒一个pthread_cond_wait上的线程,其实有些实现为了简单在单处理器上也会唤醒多个线程.
2,某些应用,如线程池,pthread_cond_broadcast唤醒全部线程,但我们通常只需要一部分线程去做执行任务,所以其它的线程需要继续wait.所以强烈推荐此处使用while循环.
其实说白了很简单,就是pthread_cond_signal()也可能唤醒多个线程,而如果你同时只允许一个线程访问的话,就必须要使用while来进行条件判断,以保证临界区内只有一个线程在处理。
一个例子如下:
<span style="font-size:14px;">#include<stdio.h> #include<stdlib.h> #include<pthread.h> #include<errno.h> #include<unistd.h> typedef void* (*fun)(void*); int x=1,y=4; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; void* thread1(void*); void* thread2(void*); int main(int argc, char** argv) { printf("enter main!\n"); pthread_t tid1, tid2; int rc1=0, rc2=0; rc2 = pthread_create(&tid2, NULL, thread2, NULL); if(rc2 != 0) printf("%s: %d\n",__func__, strerror(rc2)); rc1 = pthread_create(&tid1, NULL, thread1, &tid2); if(rc1 != 0) printf("%s: %d\n",__func__, strerror(rc1)); sleep(1); printf("leave main!\n"); exit(0); } void* thread1(void* arg) { printf("enter thread1\n"); printf("this is thread1: x= %d,y=%d. thread id is %u\n",x,y, (unsigned int)pthread_self()); pthread_mutex_lock(&mutex); x+=y; if(x>y) pthread_cond_signal(&cond); printf("this is thread1: x= %d,y=%d. thread id is %u\n", x,y, (unsigned int)pthread_self()); pthread_mutex_unlock(&mutex); pthread_join(*(pthread_t*)arg, NULL); printf("leave thread1\n"); pthread_exit(0); } void* thread2(void* arg) { printf("enter thread2\n"); printf("this is thread2: x= %d,y=%d. thread id is %u\n", x,y, (unsigned int)pthread_self()); pthread_mutex_lock(&mutex); while(x<=y) pthread_cond_wait(&cond, &mutex); x-=y; printf("this is thread2: x= %d,y=%d. thread id is %u\n", x,y, (unsigned int)pthread_self()); pthread_mutex_unlock(&mutex); printf("leave thread2\n"); pthread_exit(0); } </span>
编译时加上-lpthread参数即可。
linux线程间通信之条件变量和互斥量