生产者与消费者模型

Linux中的消费者与生产者模型

当两个线程同时访问一块临界区域的时候,一个进程往这块区域中写数据,另一个进程在里面读数据,会有很多种的情况。

被访问的这块临界区域通常叫缓冲区,而往这块缓冲区里写数据的叫生产者,在这块缓冲区里读数据的叫消费者。 
要实现消费者与生产者的关系,要满足一个原则,就是“321“原则。 3代表的是有三个关系、2代表的是两种角色、1就是一个交易场所

3种关系:生产者与生产者的关系、消费者与消费者的关系、生产者与消费 者的关系
2中角色:生产者、消费者
1个交易场所:缓冲区

其中生产者与生产者存在互斥关系、消费者与消费者之间存在互斥关系、生产者与消费者之间存在同步与互斥关系


先来想想为什么要使用消费者与生产者模型,而不是直接就用生产者调用其他方法直接把数据给消费者

1、生产者如果直接把数据给消费者,那么就要先找到消费者再把数据给他,这种方法会让生产者对消费者产生依赖,如果是多生产者和多消费者,那么就会更加的复杂。

2、而且如果当消费者正在忙时,生产者要在某个地方一直等待消费者,一直到消费者来将数据独走,这将会造成生产者的资源浪费

3、当生产者的速度太快的时候,消费者根本就忙不过来,这势必会造成有些数据还没有读到就被生产者生产的其他东西给覆盖了,当用模型的时候,生产者就会现将生产出来的数据放到缓冲区里,然后等待消费者读走,然后再继续生产。

思考一下,如果生产者往缓冲区里生产了一部分数据时,消费者就来直接读取了,那么消费者读到的数据和生产者生产的数据其实不是一个,也就是说生产者与消费者之间没有互斥的制约,所以就必须引入互斥机制,即互斥锁。

互斥锁的原型为:


其中互斥有两种等待方式,非阻塞时等待(pthread_mutex_lock),和阻塞时等待(pthread_mutex_trylock),解锁为(pthread_mutex_destroy)

当生产者的生产速度特别慢时,而消费者的读取速度特别快,这时当消费者把缓冲区里的数据读完时,生产者还没有生产出相应的数据,那么消费者就要在这里申请互斥锁来继续访问这块缓冲区,为了让消费者在读到缓冲区里没数据的时候,就挂起等待,因此引入一个概念就是条件变量。
条件变量

条件变量是判断生产者与消费在是否满足同步性。相当于消费者在等待生产者的时候,当生产者生产出来数据以后, 通过条件变量的信号去通知消费者,然后消费者被唤醒继续消费

函数原型:


条件变量既然是要唤醒正在等待的线程来继续访问这块临界区域,那么在调用pthread_cond_wait的时候,就要先把生产者的加的互斥锁先解掉,然后在设置条件变量。在这里只看pthread_cond_wait(&cond, &lock)。要让生产者发一个信号给消费者依次说明生产出数据了,所用到的函数为pthrread_cond_signal(&cond)

现来看看在一个单链表里用生产者与消费者模型来实现Push与Pop
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
typedef struct _node{
int data;
struct _node *next;
}node_t, *node_p, **node_pp;

node_p head = NULL;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

static node_p alloc_node(int _d)
{
node_p tmp = (node_p)malloc(sizeof(node_t));
if(tmp == NULL){
perror("malloc");
exit(1);
}
tmp->data = _d;
tmp->next = NULL;
}

static void delete_node(node_p _n)
{
if(_n){
free(_n);
}
}

void initList(node_pp _h)
{
*_h = alloc_node(0);
}

void PushFront(node_p _h, int _d)
{
node_p tmp = alloc_node(_d);
tmp->next = _h->next;
_h->next = tmp;
}

void PopFront(node_p _h, int *_out)
{
if(!isEmpty(_h)){
node_p p = _h->next;
_h->next = p->next;
*_out = p->data;
delete_node(p);
}
}

void showList(node_p _h)
{
node_p start = _h->next;
while(start){
printf("%d ", start->data);
start = start->next;
}
printf("\n");
}

void destroyList(node_p _h)
{
int out = 0;
while(!isEmpty(_h)){
PopFront(_h, &out);
}
delete_node(_h);
}

int isEmpty(node_p _h)
{
return _h->next == NULL ? 1 : 0;
}


void *_consume(void *arg)
{
int c;
while(1){
c = -1;
pthread_mutex_lock(&lock);
while(isEmpty(head)){
printf("consume begin waiting...\n");
pthread_cond_wait(&cond, &lock);
}
PopFront(head, &c);
pthread_mutex_unlock(&lock);
printf("consume done: %d\n", c);
///sleep(3);
}
}

void *_product(void *arg)
{
while(1){
int p = rand()%1234;
pthread_mutex_lock(&lock);
PushFront(head, p);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&cond);
printf("product done: %d\n", p);
sleep(1);
}
}


int main()
{
initList(&head);

pthread_t consume, product;

pthread_create(&consume, NULL, _consume, NULL);
pthread_create(&product, NULL, _product, NULL);

pthread_join(consume, NULL);
pthread_join(product, NULL);

pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);

destroyList(head);

return 0;
}

这里生产者生产随机生成一个数,然后用前插的方式每隔一面插到单链表里,消费者之间读取链表里的数据,如果不给消费者加条件变量,那么消费者就不不断地申请锁资源,而生产者正占用着,所以会一直请求


如果生产者生产的快,消费者消费的慢,不加条件变量那么会每隔几秒消费者读取一条消息


如果消费者给加上条件变量,那么当生产者生产一条数据,消费者被唤醒去读取一条,这样就不用消费者一直去无限制的申请锁资源,那么产生死锁的改了吧也就降低了


环形队列的生产与消费模型

刚刚的交易场所是在单链表里,如果在环形队列里时,又会有怎样的不同呢?


在学习进程间通信的时候,提到了信号量,信号量是描述临界区域的一个计数器,执行P操作的时候临界区域-1,执行V操作时+1。环形队列也是如此,它的总大小是确定的,所以他的临界区域也就确定了,在生产者生产的时候要先申请格子,如果有格子就执行相当于P操作一样的信号量,释放的时候执行像V操作一样的信号量

函数原型


sem_wait()相当于P操作,访问临界区域-1,sem_post()相当于V操作,退出时+1

这张图里的环形队列的容量大小为,刚开始时生产者与消费者都没有占格子和数据,首先生产者肯定是先生产的,当生产者把格子写满时,再去申请往临界区域里写数据时会出现格子还没有,所以就会停下来等待。当生产者从临界区域读取到数据时,然后释放一个格子,这时生产者才能往里写。

接下来写一个基于环形队列的生产者与消费者模型,让生产者不停地生产数据,消费者每隔一秒读取一条
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define SIZE 4
int ring[SIZE];
sem_t blank_sem;
sem_t data_sem;


void *consume(void *arg)
{
int step = 0;
while(1){
sleep(1);
sem_wait(&data_sem);
int data = ring[step++];
sem_post(&blank_sem);
step %= SIZE;//为了让步数在0-64之间
printf("consume done: %d\n", data);
//sleep(3);
}
}

void *product(void *arg)
{
int data = 0;
int step = 0;
while(1){
sem_wait(&blank_sem);
ring[step++] = data;
sem_post(&data_sem);
step %= SIZE;
printf("product done: %d\n", data++);
//sleep(1);
}
}


int main()
{
sem_init(&blank_sem, 0, SIZE);
sem_init(&data_sem, 0, 0);

pthread_t _consume, _product;
pthread_create(&_product, NULL, consume, NULL);
pthread_create(&_consume, NULL, product, NULL);

pthread_join(_product, NULL);
pthread_join(_consume, NULL);

sem_destroy(&blank_sem);
sem_destroy(&data_sem);
return 0;
}

可以看到和预期的一样,生产者一瞬间生产出64个数,然后消费者读一条,生产者写一条。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值