条件变量与POSIX信号量及生产者消费者模型

引入

上篇我们详细的介绍了一下Linux下线程安全以及锁机制,利用互斥锁实现了线程间的互斥,那么线程同步 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。生产出来,才能使用,保证临界资源的合理访问)到底该如何实现呢?这就是我和大家接下来要讨论的条件变量。

条件变量

概念
  条件变量本身不是锁但是它可以造成线程阻塞。没有资源则等待,生产资源后唤醒等待。条件变量通常搭配互斥锁使用,给多线程提供一个会和的场所。

条件变量操作步骤

1. 定义条件变量 在这里插入图片描述
2.初始化条件变量
在这里插入图片描述
3.阻塞等待一个条件变量
在这里插入图片描述
4.唤醒条件变量
在这里插入图片描述
5.销毁条件变量
在这里插入图片描述
条件变量为什么要搭配互斥锁使用
  因为条件变量本身只提供等待与唤醒的功能,具体什么时候等待要用户来进行判断,这个条件的判断通常涉及临界资源的操作,其它线程通常要通过修改条件来促使条件满足,而这个临界的资源的操作应该受到保护,因此要搭配互斥锁一起使用。

条件变量的优点
  相较于mutex而言,条件变量可以减少竞争。如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。

POSIX信号量

概念:POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

实质 : 进化版的互斥锁(1 --> N)
  由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据对象锁住。这样虽然达到了多线程操作共享数据时保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。
信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。

信号量操作集函数

1.定义信号量变量
在这里插入图片描述
2.初始化信号量
在这里插入图片描述
3.信号量加锁(–操作
在这里插入图片描述
4.信号量解锁(++操作
在这里插入图片描述
5.信号量销毁
在这里插入图片描述

生产者与消费者模型

引入:生产者与消费者问题是一个著名的问题,它描述的就是一群生产者生产产品,然后让消费者去消费产品的问题。

例如:生产者与消费者的问题就相当于平常的吃面问题,食堂师傅就相当于生产者,而我们的同学就相当于消费者。只有食堂师傅做好面,将面放入碗中,我们才能去吃面,否则我们就只能等待。
在这里插入图片描述
生产者消费者模型抽象
在这里插入图片描述

如何保证生产者与消费者之间的线程安全

  1. 生产者与生产者之间应该具有互斥关系
  2. 消费者与消费者之间应该具有互斥关系
  3. 生产者与消费者之间应该具有同步与互斥关系

产者与消费者模型实现

条件变量单链表实现

在这里我利用单链表模拟实现生产者与消费者模型,用链表来表示交易场所,一个线程表示生产者,生产者生产数据放入链表中,另一个线程表示消费者,消费者购买产品然后从链表中移除数据。具体实现如下:

  1 /*====================
  2  *  zhang
  3  *  生产者消费者模型
  4  *  链表存储
  5  *===================*/
  6 
  7 
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include <pthread.h>
 11 #include <unistd.h>
 12 
 13 //定义链表存储数据
 14 
 15 struct mp{
 16     int data;
 17     struct mp* next;
 18 };
 19 
 20 struct mp* head = NULL;
 21 struct mp* cur = NULL;
 22 
 23 
 24 pthread_mutex_t mutex;
 25 pthread_cond_t noodle;
 26 
 27 
 28 
 29 //生产者线程入口函数
 30 void* productor(void* arg){
 31     while(1){
 32         //生产者创建数据节点
 33         cur = malloc(sizeof(struct mp));
 34         cur->data = rand()%1000+1;
 35         printf("~~~~produce push a data:%d~~~~\n",cur->data);
 36         //放入链表中,加锁
 37         pthread_mutex_lock(&mutex);
 38         cur->next = head;
 39         head = cur;
 40         pthread_mutex_unlock(&mutex);
 41         //唤醒消费者
 42         pthread_cond_signal(&noodle);
 43         sleep(rand()%3);
 44     }
 45     return NULL;
 46 }
 47 
 48 //消费者线程入口函数
 49 void* consumer(void* arg){
 50     while(1){
 51         pthread_mutex_lock(&mutex);
 52         while(head == NULL){
 53         //  阻塞等待数据
 54             pthread_cond_wait(&noodle,&mutex);
 55         }
 56         //获取数据 (删除节点)
 57         cur = head;
 58         head = cur->next;
 59         pthread_mutex_unlock(&mutex);
 60         printf("~~~~~~consume a data:%d~~~~\n",cur->data);
 61         free(cur);
 62         cur = NULL;
 63         sleep(rand()%3);
 64     }
 65     return NULL;
 66 }
 67 
 68 
 69 int main(void){
 70 
 71     pthread_t ptid;  //生产者线程ID
 72     pthread_t ctid[4];  //消费者线程ID
 73     int ret,i;
 74     //创建生产者与消费者线程
 75     srand(time(NULL));
 76     ret = pthread_mutex_init(&mutex,NULL);
 77     if(ret != 0){
 78         printf("mutex init error!\n");
 79         return -1;
 80     }
 81     pthread_cond_init(&noodle,NULL);
 82         if(ret != 0){
 83             printf("cond init error!\n");
 84             return -1;
 85         }
 86 
 87 
 88     ret = pthread_create(&ptid,NULL,productor,NULL);
 89     if(ret != 0){
 90         printf("productor create error!\n");
 91         return -1;
 92     }
 93     for(i = 0 ; i < 4; i++){
 94         ret = pthread_create(&ctid[i],NULL,consumer,NULL);
 95         if(ret != 0){
 96             printf("productor create error!\n");
 97             return -1;
 98         }
 99 
100     }
101     //回收生产者线程
102     ret = pthread_join(ptid,NULL);
103     if(ret != 0){
104         printf("productor join error!\n");
105         return -1;
106     }
107     //回收消费者线程
108     for(i = 0 ; i < 4; i++){
109         ret = pthread_join(ctid[i],NULL);
110         if(ret != 0){
111             printf("productor join error!\n");
112             return -1;
113         }
114     }
115 
116     pthread_mutex_destroy(&mutex);
117     pthread_cond_destroy(&noodle);
118     return 0;
119 }

Posix信号量环形队列实现

利用数组模拟环形队列实现生产者与消费者模型,一个信号量表示数据资源计数器,一个信号量表示空闲空间计数器,如下图:
在这里插入图片描述
具体实现如下:

  1 /*==================
  2  *  zhangxu
  3  *  利用信号量实现简易版的
  4  *  生产者与消费者模型
  5  *  sem
  6  *=================*/
  7 
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include <unistd.h>
 11 #include <semaphore.h>
 12 #define NUM 5
 13 
 14 int queue[NUM];  //模拟环形队列
 15 sem_t sem_data;  //数据资源计数信号量
 16 sem_t sem_freespace; //空闲资源计数
 17 
 18 
 19 //生产者线程入口函数
 20 void* productor(void* arg){
 21     int i = 0 ;
 22     while(1){
 23         sem_wait(&sem_freespace);//消耗空间,空闲空间计数信号量-1
 24         queue[i] = rand()%1000 + 1;
 25         printf("~~~produce a data:%d\n",queue[i]);
 26         sem_post(&sem_data); //生产数据,数据资源计数信号量+1
 27 
 28         i = (i + 1) % NUM ;
 29         sleep(rand()%3);
 30     }
 31     return NULL;
 32 }
 33 //消费者线程入口函数
 34 void* consumer(void* arg){
 35     int i = 0;
 36     while(1){
 37         sem_wait(&sem_data); //消耗资源,数据资源计数信号量-1
 38         printf("~~~consume a data:%d\n",queue[i]);
 39         queue[i] = 0;
 40         sem_post(&sem_freespace);//消耗数据,空闲空间计数信号量+1
 41 
 42         i = (i + 1) % NUM;
 43         sleep(rand()%3);
 44     }
 45     return NULL;
 46 }
 47 
 48 
 49 
 50 int main(void){
 51 
 52     //定义生产者消费者线程
 53     pthread_t ptid,ctid;
 54     int ret;
 55     ret = sem_init(&sem_data,0,0);
 56     if(ret == -1){
 57         printf("sem_data init error!\n");
 58         return -1;
 59     }
 60     ret = sem_init(&sem_freespace,0,NUM);
 61     if(ret == -1){
 62         printf("sem_freespace init error!\n");
 63         return -1;
 64     }
 65 
 66     //创建线程
 67     ret = pthread_create(&ptid,NULL,productor,NULL);
 68     if(ret != 0){
 69         printf("porductor create error!\n");
 70         return -1;
 71     }
 72 
 73     ret = pthread_create(&ctid,NULL,consumer,NULL);
 74     if(ret != 0){
 75         printf("consumer create error!\n");
 76         return -1;
 77     }
 78 
 79     //回收线程
 80     ret = pthread_join(ptid,NULL);
 81     if(ret != 0){
 82         printf("porductor join error!\n");
 83         return -1;
 84     }
 85     ret = pthread_join(ctid,NULL);
 86     if(ret != 0){
 87         printf("consumer join error!\n");
 88         return -1;
 89     }
 90 
 91     //销毁信号量
 92     ret = sem_destroy(&sem_data);
 93     if(ret == -1){
 94         printf("sem_data destroy error!\n");
 95         return -1;
 96     }
 97     ret = sem_destroy(&sem_freespace);
 98     if(ret == -1){
 99         printf("sem_freespace destroy error!\n");
100         return -1;
101     }
102 
103     return 0;
104 }

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值