基于链表和环形队列的生产者消费者模型

    今天我们来学习一下生产者消费者模型。首先我先来给大家举一个简单的例子:

    日常生活中,当我们缺少某些生活用品时,我们都会去超市进行购买,那么大家有没有想过,你是以什么样的身份去超市的呢?相信大部分人都会说自己是消费者,的确如此,那么既然我们是消费者,又是谁替我们生产这些各种各样的商品呢?当然是超市的各大供货商了,因此他们自然而然的也就成了我们的生产者。这样一来,生产者有了,消费者也有了,那么将二者联系起来的超市又该怎么理解呢?显然,它本身是一座交易场所。那么我们可以联想到我们在使用软件的时候,代码的某个模块负责生产数据(供货商),而生产出来的数据却不得不交给另一个模块(消费者)来对其进行处理,在这之间我们必须要有一个类似上述超市的东西来存储数据(超市),这就是我们今天要学习的生产者/消费者模型。 其中,产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者生产者和消费者之间的中介就叫做缓冲区。这样一来,我们就很容易理解什么事生产者消费者模型了。

现在我们要了解的是他们三个之间的关系:

1、生产者和生产者之间的关系

    我们拿刚才所举的例子为例来解释一下,供货商相当于生产者。假设所有的供货商都生产同一种商品,而超市只能由一家供货商供货,他们之间肯定互相竞争这个名额,因此他们之间存在着互斥关系。

2、消费者和消费者之间的关系

    和生产者类似,如果现在超市中只有一件商品了,而所有的消费者都想拥有这件商品,所以他们也会去互相竞争,这就存在着互斥关系。

3、生产者和消费者之间的关系

    再来看看生产者和消费者之间的关系,现在我们假想一下如果超市的货架上一件商品都没有,我们可以消费吗?再想一想超市的货架上面商品都摆的满满的,供货商还会继续摆商品吗?答案是不会,所以我们就可以想到,消费者必须等生产者生产出这个商品以后才能消费,而生产者必须等到消费者消费完商品之后才能继续生产,这就是典型的同步关系。同样的生产者和消费者之间还具有着互斥关系 。在生产者把商品放到货架上的时候消费者不能去消费,必须等生产者放完之后才可以消费,而在消费者把商品从货架上拿走的时候生产者不能去放商品,必须等消费者完全拿走之后才可以放。

生产者/消费者模型原则:

三二一原则:三种关系、两种角色、一个场所

三种关系: 
1、生产者与生产者之间存在互斥关系 
2、生产者与消费者之间存在同步和互斥关系 
3、消费者与消费者之间存在互斥关系

两种角色: 
1、生产者 
2、消费者

一个场所: 

1、缓冲区


如果大家还不明白,我再来举一个 寄信的例子,假设你要寄一封信,大致过程如下: 
       1)写好信——相当于生产者制造数据; 
       2)把信放入邮筒——相当于生产者把数据放入缓冲区; 
       3)邮递员把信从邮筒取出——相当于消费者把数据取出缓冲区; 
       4)邮递员把信拿到邮局做相应的处理——相当于消费者处理数据。 

       可能有人会有疑问为什么需要用到缓冲区呢?那么通过寄信的例子就可以很容易理解了,如果不需要邮筒,每个人寄信都要亲自将信直接交给邮递员,那么基本上就需要每个人都配有一个邮递员了,显然是不可能的啦~~  这时我相信大家应该都理解了这个过程,所以大家肯定也就理解了生产者消费者模型啦!!

    大家理解了生产者消费这模型之后,我们来简单实现一下,我们可以把超市想象成一个单链表,生产者就是向链表了插入数据,消费者就是从链表中删除数据。我们再利用线程来模拟生产者和消费者,利用之前说过的互斥量和条件变量来维护互斥和同步。互斥&同步&信号量

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>

//初始化互斥量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

//链表节点
typedef struct Node
{
    int data;
    struct Node* next;
}Node,*pNode,**ppNode;

//申请节点
pNode BuyNode(int x)
{
    pNode newNode=(pNode)malloc(sizeof(Node));
    if(newNode==NULL)
    {
        perror("malloc");
        exit(1);
    }
    newNode->data=x;
    newNode->next=NULL;
    return newNode;
}
//初始化链表
void InitList(ppNode head)
{
    *head=BuyNode(0);
}
//链表判空
int IsEmpty(pNode head)
{
    return head->next==NULL?1:0;
}
//往链表中插入元素---头插
void pushList(pNode head,int x)
{
    pNode n=BuyNode(x);
    n->next=head->next;
    head->next=n;
}
//从链表中删除元素---头删
int popList(pNode head,int* x)
{
    if(IsEmpty(head))
        return -1;

    pNode n=head->next;
    head->next=n->next;
    *x=n->data;
    free(n);
    return 0;
}

//消费者
void* consumer(void* arg)
{
    pNode lhead = *((ppNode)arg);
    int d=0;
    while(1)
    {
        pthread_mutex_lock(&mutex);//加锁
        while(lhead->next==NULL)//如果链表为空
        {
            printf("consumer need to wait...\n");
            pthread_cond_wait(&cond,&mutex);//消费者挂起等待
        }
        popList(lhead,&d);//不为空消费数据
        printf("consumer get data:%d\n",d);
        sleep(2);
    }

}

//生产者
void* produce(void* arg)
{
    pNode lhead = *((ppNode)arg);
    int d=0;
    while(1)
    {
        sleep(1);//为了演示消费者等待让生产者先运行
        d = rand()%100+1;
        pushList(lhead,d);//生产数据
        printf("product data:%d\n",d);
        pthread_cond_signal(&cond);//唤醒消费者
        pthread_mutex_unlock(&mutex);//解锁
        sleep(3);
    }
}

int main()
{
    pthread_t c,p;

    pNode head=NULL;
    InitList(&head);

    srand((unsigned long)time(NULL));

    pthread_create(&c,NULL,runC,(void*)&head);
    pthread_create(&p,NULL,runP,(void*)&head);

    pthread_join(c,NULL);
    pthread_join(p,NULL);

    return 0;
}

POSIX信号量

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

初始化信号量

#include<semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value)
参数
    pshared:0表示线程间共享,非零表示进程间共享
    value:信号量初始值

销毁信号量

int sem_init(sem_t *sem)

等待信号量

#include<semaphore.h>
int sem_wait(sem_t *sem)
功能:等待信号量,会将信号量的值减1

发布信号量

#include<semaphore.h>
int sem_post(sem_t *sem)
功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量的值加1。

    刚刚我们实现的生产者消费者模型是基于单链表的。现在我们来看看基于环形队列的生产者消费者模型。

首先来谈谈环形队列的概念,上一张图: 


    最开始的时候,我们的队列里面没有元素,所以必须让生产者先生产数据,然后消费者跟在生产者的后面开始存储数据。如果生产者一直生产,消费者没有消费的时候,这个队列就会满,那么此时生产者就不能再生产了,只能等消费者消费之后露出空格,再去生产数据。我们可以发现的是环形队列只需要一个类似于计数器的东西来标记数据总量和空格总量就可以实现生产者和消费者之间的同步和互斥机制了,很容易就可以想到利用信号量来实现。

如果大家还没有理解环形队列,这里我们在重新举个例子解释一下:将生产者写入(R)与消费者读出(R)想象成在田径场跑道上赛跑的两个人(R追逐W)。当R追上W的时候,就是缓冲区为空;当W追上R的时候(W比R多跑圈),就是缓冲区满。 

那我们现在来实现一下:

1、单生产者单消费者

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<semaphore.h>

#define SIZE 6                                                                                            
int arr[SIZE] = {0};
int ret = 0;
sem_t datasem;
sem_t blanksem;

void *productor(void *arg)
{
    int i = 0;
    while(1)
    {  
        sem_wait(&blanksem);
        arr[ret] = i;
        printf("productor data %d\n",arr[ret]);
        sem_post(&datasem);
        i++;
        ret++;
        ret %= SIZE;
    }
}

void *consumer(void *arg)
{
    while(1)
    {                                                                                                     
        sem_wait(&datasem);
        printf("consumer data %d\n",arr[ret]);
        sem_post(&blanksem);
        sleep(1);
    }
}

int main()
{
    pthread_t product, consume;

    sem_init(&datasem, 0, 0);
    sem_init(&blanksem, 0, SIZE);

    pthread_create(&product, NULL, productor, NULL);
    pthread_create(&consume, NULL, consumer, NULL);

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

    sem_destroy(&datasem);
    sem_destroy(&blanksem);
    return 0;
} 

结果如下:


2、多生产者多消费者

    编写多生产者多消费者模型,我们还需要维持生产者与生产者之间的互斥关系,消费者与消费者之间的互斥关系,因此我们需要用到互斥量。直接看代码吧~

#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <time.h>
#include <pthread.h>

#define M 10
#define C 3
#define P 3
int ring[M];//环形队列,利用数组和%运算实现

sem_t sem_data;//数据量
sem_t sem_blank;//空格子量

pthread_mutex_t lock1=PTHREAD_MUTEX_INITIALIZER;//消费者之间的互斥
pthread_mutex_t lock2=PTHREAD_MUTEX_INITIALIZER;//生产者之间的互斥

//消费者
void* consumer(void* arg)
{
    static int i=0;
    int d;
    while(1)
    {
        pthread_mutex_lock(&lock1);//加锁,必须给临界区的所以代码加锁
        sem_wait(&sem_data);//消费者申请数据资源
        d=ring[i];
        printf("consumer data:%d\n",d);
        i++;
        i%=M;//模运算,防止下标越界
        sem_post(&sem_blank);//消费数据后对空格资源V操作
        pthread_mutex_unlock(&lock1);
        sleep(3);
    }
}
//生产者
void* product(void* arg)
{
    int data=0;
    static int i=0;
    while(1)
    {
        pthread_mutex_lock(&lock2);
        data=rand()%100+1;
        sem_wait(&sem_blank);//生产者申请空格资源
        ring[i]=data;
        printf("product data:%d\n",data);
        i++;
        i%=M;//模运算。防止下标越界
        sem_post(&sem_data);//生产者生产完后对数据资源V操作
        pthread_mutex_unlock(&lock2);
        sleep(2);
    }
}

int main()
{
    srand((unsigned long)time(NULL));

    pthread_t consumer[C];
    pthread_t product[P];
    int i=0;
    for(i=0;i<C;i++)//创建多个消费者线程
    {
        pthread_create(&consumer[i],NULL,runC,NULL);
    }
    for(i=0;i<P;i++)//创建多个生产者线程
    {
        pthread_create(&product[i],NULL,runP,NULL);
    }
    sem_init(&sem_data,0,0);
    sem_init(&sem_blank,0,M);

    for(i=0;i<C;i++)
    {
        pthread_join(consumer[i],NULL);
    }
    for(i=0;i<P;i++)
    {
        pthread_join(product[i],NULL);
    }

    sem_destroy(&sem_data);
    sem_destroy(&sem_blank);

    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/lu_1079776757/article/details/79918869