Linux学习—条件变量实现同步

       与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
       条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
       条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
       使用条件变量之前要先进行初始化。可以在单个语句中生成和初始化一个条件变量如:pthread_cond_t my_condition=PTHREAD_COND_INITIALIZER;(用于进程间线程的通信)。可以利用函数pthread_cond_init动态初始化。
       条件变量分为两部分: 条件和变量. 条件本身是由互斥量保护的. 线程在改变条件状态前先要锁住互斥量. 它利用线程间共享的全局变量进行同步的一种机制。

相关的函数如下:

1 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);     
2 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
3 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
4 int pthread_cond_destroy(pthread_cond_t *cond);  
5 int pthread_cond_signal(pthread_cond_t *cond);
6 int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有线程的阻塞

简要说明:
(1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;属性置为NULL
(2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真
timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
(3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
(4)清除条件变量:destroy;无线程等待,否则返回EBUSY

详细说明
1.初始化:

条件变量采用的数据类型是pthread_cond_t, 在使用之前必须要进行初始化, 这包括两种方式:

静态: 可以把常量PTHREAD_COND_INITIALIZER给静态分配的条件变量.

动态: pthread_cond_init函数, 是释放动态条件变量的内存空间之前, 要用pthread_cond_destroy对其进行清理.

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);

成功则返回0, 出错则返回错误编号.

当pthread_cond_init的attr参数为NULL时, 会创建一个默认属性的条件变量; 非默认情况以后讨论.

2.等待条件:

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restric mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout);

成功则返回0, 出错则返回错误编号.

这两个函数分别是阻塞等待和超时等待. 等待条件函数等待条件变为真, 传递给pthread_cond_wait的互斥量对条件进行保护, 调用者把锁住的互斥量传递给函数. 函数把调用线程放到等待条件的线程列表上, 然后对互斥量解锁, 这两个操作是原子的. 这样便关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道, 这样线程就不会错过条件的任何变化.

当pthread_cond_wait返回时, 互斥量再次被锁住.

3.通知条件:

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

成功则返回0, 出错则返回错误编号.

这两个函数用于通知线程条件已经满足. 调用这两个函数, 也称向线程或条件发送信号. 必须注意, 一定要在改变条件状态以后再给线程发送信号.

条件变量实现同步实例—控制方便面的买卖

// pthread_cond_t cond;  定义一个条件变量
   14 // pthread_cond_init;   初始化条件变量
   15 // pthread_cond_destroy  销毁
   16 // pthread_cond_wait    等待
   17 // pthread_cond_signal  通知  等待在条件变量 上的线程
   18 
   19 
   20 
   21 
   22 #include<stdio.h>
   23 #include<unistd.h>
   24 #include<pthread.h>
   25 #include<stdlib.h>
   26 
   27 int g_judge=0;               //0代表没有方便面  1代表有方便面
   28 pthread_mutex_t mutex;
   29 pthread_cond_t full;
   30 pthread_cond_t empty;
   31 
   32 
W> 33 void* sale_noodles(void* arg)
   34 {
   35   
   36  while(1)
   37  {
   38     // usleep(300);
   39     
   40    pthread_mutex_lock(&mutex);                                                                                                                              
   41 
   42    while(g_judge==1)
    //先解锁
   47      //集合了 解锁+ 休眠+ 被唤醒后加锁  的原子操作
   48      pthread_cond_wait(&full,&mutex);                        //这个 mutex 有什么用?    --->   ++g_judge不是一个原子操作,涉及到对临界资源的修改,多对多情况>      下不安全  
   49 
   50 
   51    }
   52      printf("product one noodle\n");
   53      g_judge++;
   54      //因为买方便面一直在wait死等,所以还需要通知一下
   55      pthread_cond_signal(&empty);
E> 56      pthread_mutex_unlock(mutex);
   57  
   58  }
   59 
   60 
   61      return NULL;
   62 
   63 }
   64 
   65 
   66 
E> 67 
   68 
   69 
   70 
E> 71 void* buy_noodles(void* arg)
   72 {                                                                                                                                                           
   73  
   74 
   75                                                           
   76 
   77 while(1)
{
pthread_mutex_lock(&mutex);                //这块为什么加锁,涉及到对临界资源的访问
   80   
   81   //如果没有方便面,就要等待
   82   while(g_judge==0)                     //必须得用 while 判断  因为有很多线程不停的判断
   83    {
   84       //int pthread_cond_wait( ...  )
   85     pthread_cond_wait(&empty,&mutex);                //解锁 和 挂起等待 必须是一个原子操作 ,否则不安全    &&  完成之后如果被唤醒会重新尝试加锁
   86   
   87    
   88    
   89    }
   90 
   91   
   92   printf("eat one noodle~~~\n");
   93   g_judge--;
   94   pthread_cond_signal(&full); 
   95   pthread_mutex_unlock(&mutex);
   96   
   97 }
   98 
   99 
  100   return NULL;
  101 
  102 }
  103 
         int main()
  115 {
  116  
  117   pthread_cond_init(&full,NULL); 
  118   pthread_cond_init(&empty,NULL); 
  119   pthread_mutex_init(&mutex,NULL);
  120   
  121   pthread_t tid1,tid2;
  122   pthread_create(&tid1,NULL,sale_noodles,NULL);
E>123   pthread_create(&tid2,NULL,buy_noodles,NULL);
  124 
  125   pthread_join(tid1,NULL);
  126   pthread_join(tid2,NULL);
  127 
  128   pthread_mutex_destroy(&mutex);
  129   pthread_cond_destroy(&full);
  130   pthread_cond_destroy(&empty);
  131 
  132 }

注意:我们有创建两个条件变量empty和full。买卖双方wait的时候分别在各自的条件变量上等待。如果买卖双方都同时等在同一个条件变量上的话,在多线程的情况下会容易出现BUG;还有一点就是pthread_cond_wait为什么参数中还有mutex,是因为它集合了 解锁+ 休眠+ 被唤醒后加锁 的原子操作。

//集合了 解锁+ 休眠+ 被唤醒后加锁  的原子操作
    pthread_cond_wait(&full,&mutex);   

猜你喜欢

转载自blog.csdn.net/tonglin12138/article/details/93013197