锁并不是并发程序设计所需要的唯一原语
很多时候 线程需要检查某一条件满足之后 才会继续运行
可以尝试用 共享变量 但是效率很低 因为主线程会自旋检查 浪费CPU时间
30.1 定义和程序
条件变量是一个显式队列,当某些状态不满足时,线程可以把自己加入队列,等待该条件;另外某些线程改变了上述状态时,就可以唤醒一个或多个等待线程(通过在该条件上发信号)。
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);//使调用线程进入休眠状态
int pthread_cond_signal(pthread_cond_t *cond);
- 要使用条件变量 必须另外有一个与此条件相关的锁 在调用wait和signal时必须持有锁
- 等待调用将锁作为第二个参数 而信号调用只需要一个参数:因为等待调用除了使线程进入睡眠状态外,还会让调用者在睡眠的时候释放锁(不然其他线程怎么获得锁 修改临界区内容从而唤醒该线程捏?)
- 在被唤醒之后、返回之前。pthread_cond_wait()会重新获得该锁 确保整个过程都持有锁
30.2 生产者/消费者(有界缓冲区)问题
- Mesa语义:
如果将wait和signal放入if中,将会产生此语义。假设一种情况有一个生产者和多个消费者。在consumer1被生产者唤醒后,但是在它运行之前,缓冲区的状态改变了(因为consumer2),那么consumer1醒后将会触发assert。发信号给线程只是唤醒它们,暗示状态发生了变化,但是并不会保证在它运行之前状态一直是期望的情况
较好但仍有问题的解决方案:使用While语句代替If
- 当将if改为while后,当consumer1被唤醒后,会第一时间检查共享变量,如果此时缓冲区为空,消费者就会回去继续睡眠
由于Mesa语义,关于条件变量有一条规则:总是使用while循环
单值缓冲区的生产者/消费者方案:
如果生产者和消费者使用同一个条件变量的话,可能会导致三个线程同时睡眠。所以**生产者和消费者应该使用不同的条件变量**
最终的生产者/消费者方案:
**提高并发和效率** 增加更多缓冲区槽位,这样睡眠之前可以生产多个值,同样睡眠之前也可以消费多个值。
- ①单个消费者和生产者时,上下文切换少,提高效率
- ②多个消费者和生产者时,支持并发生产和消费,从而提高了并发
//最终的put()和get()方法
int buffer[MAX];
int fill=0;
int use=0;
int count=0;
void put(int value){
buffer[fill]=value;
fill=(fill+1)%MAX;
}
int get(){
int tmp=buffer[use];
use=(use+1)%MAX;
count--;
return tmp;
}
//最终有效方案
pthread_cond_t empty,fill;
pthread_mutex_t mutex;
void *producer(void *arg){
int i;
for(i=0;i<loops;++i){
pthread_mutex_lock(&mutex);
while(count==MAX)
pthread_cond_wait(&empty,&mutex);
put(i);
pthread_cond_signal(&fill);
pthread_mutex_unlock(&mutex);
}
}
void *consumer(void *arg){
int i;
for(i=0;i<loops;++i){
pthread_mutex_lock(&mutex);
while(count==0)
pthread_cond_wait(&fill,mutex);
int tmp=get;
pthread_cond_signal(&empty);
pthread_mutex_unlock(mutex);
return (void*)tmp;
}
}