锁并不是并发程序设计所需要的唯一原语

很多时候 线程需要检查某一条件满足之后 才会继续运行

可以尝试用 共享变量 但是效率很低 因为主线程会自旋检查 浪费CPU时间

30.1 定义和程序

条件变量是一个显式队列,当某些状态不满足时,线程可以把自己加入队列,等待该条件;另外某些线程改变了上述状态时,就可以唤醒一个或多个等待线程(通过在该条件上发信号)。

  1. int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);//使调用线程进入休眠状态
  2. 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循环


单值缓冲区的生产者/消费者方案:

  1. 如果生产者和消费者使用同一个条件变量的话,可能会导致三个线程同时睡眠。所以**生产者和消费者应该使用不同的条件变量**

最终的生产者/消费者方案:

  1. **提高并发和效率** 增加更多缓冲区槽位,这样睡眠之前可以生产多个值,同样睡眠之前也可以消费多个值。
  • ①单个消费者和生产者时,上下文切换少,提高效率
  • ②多个消费者和生产者时,支持并发生产和消费,从而提高了并发
  1. //最终的put()和get()方法
  2. int buffer[MAX];
  3. int fill=0;
  4. int use=0;
  5. int count=0;
  6. void put(int value){
  7. buffer[fill]=value;
  8. fill=(fill+1)%MAX;
  9. }
  10. int get(){
  11. int tmp=buffer[use];
  12. use=(use+1)%MAX;
  13. count--;
  14. return tmp;
  15. }
  1. //最终有效方案
  2. pthread_cond_t empty,fill;
  3. pthread_mutex_t mutex;
  4. void *producer(void *arg){
  5. int i;
  6. for(i=0;i<loops;++i){
  7. pthread_mutex_lock(&mutex);
  8. while(count==MAX)
  9. pthread_cond_wait(&empty,&mutex);
  10. put(i);
  11. pthread_cond_signal(&fill);
  12. pthread_mutex_unlock(&mutex);
  13. }
  14. }
  15. void *consumer(void *arg){
  16. int i;
  17. for(i=0;i<loops;++i){
  18. pthread_mutex_lock(&mutex);
  19. while(count==0)
  20. pthread_cond_wait(&fill,mutex);
  21. int tmp=get;
  22. pthread_cond_signal(&empty);
  23. pthread_mutex_unlock(mutex);
  24. return (void*)tmp;
  25. }
  26. }