线程同步: 条件变量cond


1.为什么引出条件变量?

举个简单的例子,应用程序A中包含两个线程t1和t2。t1需要在 bool变量test_cond为true时才能继续执行,而test_cond的值是由t2来改变的,这种情况下,如何来写程序呢?可供选择的方案有两 种:
(1)第一种是t1定时的去轮询变量test_cond,如果test_cond为false,则继续休眠;如果test_cond为 true,则开始执行。
(2)第二种就是上面提到的条件变量,t1在test_cond为false时调用cond_wait进行等待,t2在改变test_cond的值后,调用cond_signal,唤醒在等待中的t1,告诉t1 test_cond的值变了,这样t1便可继续往下执行。
[性能对比分析]
很明显,上面两种方案中,第二种方案是比较优的。在第一种方案中,在每次轮询时,如果t1休眠的时间比较短,会导致cpu浪费很厉害;如果t1休眠的时间 比较长,又会导致应用逻辑处理不够及时,致使应用程序性能下降。第二种方案就是为了解决轮询的弊端而生的。

2.条件变量

1.条件变量允许线程以无竞争的方式等待特定的条件发生。(条件本身是由互斥量保护的。线程在改变条件变量状态前必须先锁住互斥量。)
2.条件变量是在多线程程序中用来实现**“等待->唤醒”**逻辑常用的方法(条件变量不是锁!它可以造成线程阻塞,通常与互斥锁配合使用)
3.条件变量+互斥锁(一起使用)
互斥锁:线程同步
条件变量:阻塞线程
4.条件变量主要包括两个动作:一个线程等待“条件变量的成立”,条件不成立时线程挂起;另一个线程修改条件,并判断条件是否成立,当“条件成立”时,唤醒等待条件成立的线程。

条件变量的万能公式
# 消费者代码

while True:
	pthread_mutex_lock(mutex)         
	while(条件不满足){
	    pthread_cond_wait(cond, mutex);
	} 
	修改条件
	pthread_mutex_unlock(mutex)

# 生产者代码

while True:
	pthread_mutex_lock(mutex) 
	修改条件
	if(条件满足){
		pthread_cond_signal(&cond)
	}
	pthread_mutex_unlock(&mutex);  

3.条件变量API

pthread_cond_t cond; //条件变量类型

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); //初始化
int pthread_cond_destroy(pthread_cond_t *cond); //销毁

//当条件不到来时,仅仅只阻塞一定的时长
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
//当条件不到来时,一直阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);

//唤醒[至少一个]阻塞在条件变量上等待的线程
int pthread_cond_signal(pthread_cond_t *cond);
//广播:唤醒[全部]的阻塞在条件变量上等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);

[注1] pthread_cond_wait,内核会做下面这些事
1.拿到锁的线程,把锁暂时释放;
2.线程休眠,进行等待;
3.线程等待通知,要醒来。(重新获取锁)

[注2] pthread_cond_timedwait函数的参数abstime

struct timespec {  //绝对时间
   time_t tv_sec;        /* seconds */long tv_nsec;    /* nanosecondes*/ 纳秒
}     

相对时间/绝对时间:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟

abstime的正确用法

time_t cur = time(NULL); //获取当前时间。
struct timespec t;    //定义timespec 结构体变量t
t.tv_sec = cur+1; //定时1秒

pthread_cond_timedwait (&cond, &mutex, &t); //传参

4.示例代码

4.1 [单生产者/单消费者]单链表的插入和删除
/*作为公享数据,需被互斥量保护*/
  typedef struct node{//声明链表类型 
    int data;      
    struct node* next;       
  }LNode;
         
  LNode* head=NULL; //临界资源:链表的头节点   
         
  pthread_mutex_t mutex;     
  pthread_cond_t cond;       
         
  void* producer(void* arg){ 
    while(1){      
      LNode* node=(LNode*)malloc(sizeof(LNode)); //创建新的节点node        
      node->data=rand()%100; 
         
      pthread_mutex_lock(&mutex);      
         
      node->next=head; //将node采用头插法插入链表
      head=node;   
      printf(" produce node=%d\n",node->data);   
         
      pthread_mutex_unlock(&mutex);    
         
      sleep(rand()%3);       
  
      pthread_cond_signal(&cond); //插入node后,唤醒等待的线程去消费链表
    }
    return NULL;
  }
  void* customer(void* arg){ 
    while(1){      
      pthread_mutex_lock(&mutex);      
      while(head==NULL){ //if(head==NULL)  如果链表为NULL,则一直阻塞
        pthread_cond_wait(&cond,&mutex);         
      }  
	  //采用头删法删除节点
      LNode* delNode=head;   
      head=head->next;  //delete head  
      printf("delete node=%d\n",delNode->data);  
      free(delNode);         
         
      pthread_mutex_unlock(&mutex);    
         
      sleep(rand()%3);       
    }    
    return NULL;   
  }      
  int main(){      
    pthread_mutex_init(&mutex,NULL);   
    pthread_cond_init(&cond,NULL);
         
    pthread_t tid1,tid2;     
         
    pthread_create(&tid1,NULL,producer,NULL);    
    pthread_create(&tid2,NULL,customer,NULL);    
         
    pthread_join(tid1,NULL);         
    pthread_join(tid2,NULL);
         
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);    
         
    return 0;      
  }      
4.2 条件变量之(多生产者/多消费者模型)

1.两个条件变量cond_full/cond_empty,分别控制缓冲区满/空
2.mutex控制全局变量g_count(消费者将g_count每次减去1,生产者将g_count每次加1)

为什么要使用互斥锁配合条件变量使用?
因为涉及到多个线程对全局变量g_count进行操作,所以要用线程互斥锁对g_count进行控制;所以首先定义互斥锁mutex,然后调用pthread_mutex_lock(&mutex)进行上锁,对g_count进行操作之后再调用pthread_mutex_unlock(&mutex)进行解锁;

代码细节:
1.先创建消费者线程后sleep(3),此时缓冲区中无产品,因此消费者会一直阻塞
2.消费者线程数=2,生产者线程数=3
3.生产者生产完产品后sleep(1),消费者消费完产品后sleep(3)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
 
#include <signal.h>

#define MAXSIZE 10

#define CUSTOM_COUNT 2
#define PRODUCT_COUNT 3 

int produce_id = 0;
int custome_id = 0;

int g_count = 0; //全局变量

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;    // 一个互斥锁
pthread_cond_t cond_full = PTHREAD_COND_INITIALIZER;  // 两个条件变量
pthread_cond_t cond_empty=PTHREAD_COND_INITIALIZER;
void *consume(void *arg)
{
  int id = ++custome_id;
  while(1)
  {
    pthread_mutex_lock(&mutex);
    while(g_count == 0) //醒来以后需要重新判断条件是否满足,如果不满足,再次等待
     {
      printf("    buffer is empty, [%d consumer] waiting...\n", id);
      pthread_cond_wait(&cond_empty, &mutex);
     }
    printf("    [%d consumer] consume [g_count=%d]  \n", id, g_count);
    g_count--;
    pthread_cond_signal(&cond_full);
    pthread_mutex_unlock(&mutex);	
    sleep(3);
  }
}
 
void *produce(void *arg)
{
  int id = ++produce_id;
  while(1)
  {
    pthread_mutex_lock(&mutex);
    while(g_count >= MAXSIZE)
     {
      printf("buffer is full, [%d producer] waiting...\n", id);
      pthread_cond_wait(&cond_full,&mutex);
     }
    g_count++;
    printf("[%d producer] produce [g_count=%d]  \n", id, g_count);
    pthread_cond_signal(&cond_empty);
    pthread_mutex_unlock(&mutex);
    sleep(1);
  } 
}
 
void handler(int signo)
{
  if(signo==SIGINT)
  {
    pthread_cond_destroy(&cond_full);
    pthread_cond_destroy(&cond_empty);
    exit(0);
  }
}
int main()
{
  signal(SIGINT,handler);
  int i = 0;
  pthread_t tidCustom[CUSTOM_COUNT];
  pthread_t tidProduce[PRODUCT_COUNT];
	
  for (i = 0; i < CUSTOM_COUNT; ++i) /*创建消费者线程*/
	pthread_create(&tidCustom[i], NULL, consume, NULL);
  sleep(3);

  for (i = 0; i < PRODUCT_COUNT; ++i) /*创建生产者线程*/
	pthread_create(&tidProduce[i], NULL, produce, NULL);

  for (i = 0; i < CUSTOM_COUNT; ++i) /*等待消费者线程*/
	pthread_join(tidCustom[i], NULL);	
  for (i = 0; i < PRODUCT_COUNT; ++i) /*等待生产者线程*/
	pthread_join(tidProduce[i], NULL);
	  
  return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值