在说今天的主题之前呢,我们先来看看什么是生产者消费者模型。
用自己的话总结来说,生产者消费者模型可以被称为“三二一原则”
“三”是指有三个原则,这三个原则分别是:
(1)生产者与生产者之间是互斥的关系。
(2)生产者与消费者之间是同步与互斥关系。
(3)消费者与消费者是互斥的关系。
“二”是指两种角色:生产者和消费者
“一”是指一个交易场所
接下来让我们先看看基于单链表的生产者消费者模型吧!
基于单链表的生产者消费者模型的意思是:首先创建一条单链表,生产者往这条单链表放入结点,而消费者从这条单链表中取出结点。
在实现此模型之前我们需要先了解两个背景知识。
一、互斥锁
互斥锁的功能是完成互斥功能。
(1)初始化互斥锁
它有两种方式:一种只适用于初始化全局变量,一种适用于初始化局部或者全局变量。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//只适用于初始化全局变量
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
(2)加锁——pthread_mutex_lock函数
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
(3)解锁——pthread_mutex_unlock函数
int pthread_mutex_unlock(pthread_mutex_t *mutex);
(3)pthread_mutex_trylock函数
一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。
如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,⽽而不会使线程挂起等待。
函数原型为:
int pthread_mutex_trylock(pthread_mutex_t *mutex);
二、条件变量
条件变量是用来描述临界资源状态的,它具有原子性。
为什么要有条件变量呢?在我们消费者消费的时候,如果单链表中一个结点都没有,那么消费就会失败,所以我们要创建一个条件变量来表示消费者在什么情况才能消费,如果条件变量的条件不满足那么线程就会进行等待,直到有人唤醒它为止。那么我们就来看看关于条件变量的相关操作吧!
(1)初始化条件变量
它和Mutex的初始化和销毁类似,pthread_cond_init函数初始化一个Condition Variable,attr参数 为NULL则表示缺省属性,pthread_cond_destroy函数销毁一个Condition Variable。
如果条件变量是静态分配的,也可以⽤用宏定义PTHEAD_COND_INITIALIZER初始化。
(2)等待
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
可见,一个条件变量总是和一个Mutex搭配使用的。一个线程可以调用
pthread_cond_wait在一个条件变量上阻塞等待,这个函数做以下三步操作:
1. 释放Mutex
2. 阻塞等待
3. 当被唤醒时,重新获得Mutex并返回
(3)唤醒
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒多个线程
int pthread_cond_signal(pthread_cond_t *cond);//唤醒一个线程
好啦,了解完以上背景知识,我们先来看看简单的生产者消费者模型吧!具体代码如下:
(1)创建MakeFile
mycp:mycp.c
gcc -o $@ $^ -lpthread
.PHONY:clean
clean:
rm -f mycp
(2)创建mycp.c
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;//初始化互斥锁
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;//初始化条件变量
typedef struct _node
{
int data;
struct _node *next;
}node_t,*node_p,**node_pp;//创建链表结构体
node_p head;//创建head链表
static node_p alloc_node( int data)//初始化链表
{
node_p tmp=(node_p)malloc(sizeof(node_t));
if(!tmp)
{
perror("malloc");
exit(1);
}
tmp->data=data;
tmp->next=NULL;
}
void initList(node_pp _h)//初始化链表
{
*_h=alloc_node(0);
}
void pushFront(node_p h,int data)//向链表头部插入元素
{
node_p _n=alloc_node(data);
_n->next=h->next;
h->next=_n;
}
static void free_node( node_p _n)//释放结点
{
if(_n)
{
free(_n);
}
}
int isEmpty( node_p h)//判断链表是否为空
{
return h->next==NULL?1:0;
}
void popFront(node_p h,int *out)//从链表头部删除元素
{
if(!isEmpty(h))
{
node_p _n=h->next;
h->next=_n->next;
*out=_n->data;
free_node( _n);
}
}
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;
while(!isEmpty(h))
{
popFront(h,&out);
}
free_node(h);
}
void *product(void *arg)//生产者
{
while(1)
{
int data=rand()%1234;//产生随机数
pthread_mutex_lock(&lock);//加锁
pushFront(head,data);//将产生的随机数头插到链表中(相当于生产)
pthread_mutex_unlock(&lock);//解锁
pthread_cond_signal(&cond);//生产完后唤醒消费者
printf("The product success:%d\n",data);
sleep(1);//1s生产一个
}
}
void *consume(void *arg)//消费者
{
while(1)
{
int data=-1;//消费失败则输出-1
pthread_mutex_lock(&lock);
while(isEmpty(head))
{
printf("no data,The consume wait.......\n");
pthread_cond_wait(&cond,&lock);//链表中无结点等待
}
popFront(head,&data);
pthread_mutex_unlock(&lock);
printf( "The consume success:%d\n",data);
}
}
int main( )
{
pthread_t p;//定义p线程
pthread_t c;//定义c线程
pthread_create(&p,NULL,product,NULL);//创建线程
pthread_create(&c,NULL,consume,NULL);//创建线程
initList(&head);
destroyList(head);
pthread_join(p,NULL);//线程被等待
pthread_join(c,NULL);//线程被等待
return 0;
}
(3)运行成功可得到的结果是:
上述就是关于基于单链表的生产者消费者模型,接下来我们来说说基于环形队列的生产者消费者模型。
什么是环形队列呢?如图所示
每个格子就相当于超市的货架,当每个货架为空时,生产者才能生产,当每个货架有东西时,消费者才能消费,且生产者与消费者必须同步,且必须生产者快于消费者。
要符合环形队列的生产者消费者模型,生产者和消费者必须满足以下三个条件:
(1)生产者必须快于消费者
(2)生产者不能将消费者套圈
(3)消费者不能消费时,生产者先走。
生产者满时(不能在生产时),消费者先走。
要完成这一模型,我们必须还要了解一些关于Semaphore(信号量)的知识。
semaphore变量的类型为sem_t。
初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
value参数表.示可用资源的数量,pshared参数为0表示信号量用于同一进程的线程。
在用完semaphore变量之后应该调用sem_destroy()释放与semaphore相关的资源。
调用sem_wait()可以获得资源(P操作),使semaphore的值减1,如果调用sem_wait()时semaphore的值已经是0,则挂起等待。如果不希望挂起等待,可以调用sem_trywait() 。
调用sem_post() 可以释放资源(V操作),使semaphore 的值加1,同时唤醒挂起等待的线程。
生产者关心的是货架是否为空(格子是否空)所以生产者的大致做法是:
对空位数减1,放入数据,然后格子中含有数据的个数就加1。
消费者关心的是数据是否为空,所以消费者的大致做法是:对格子中含有数据的个数减1,拿出数据,然后空格子数加1。
在了解如上知识后,我们来看看基于环形队列的生产者消费者模型的相关代码吧!
(1)创建Makefile
ring:ring.c
gcc -o $@ $^ -lpthread
.PHONY:clean
clean:
rm -f ring
(2)创建ring.c
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#define SIZE 64
int ring[SIZE];//创建数组,存放数据
sem_t blank_sem;//定义信号量,表示空格子量
sem_t data_sem;//定义信号量,表示格子中含有数据的个数
void *product( void *arg)//生产者
{
int data=0;
int step=0;
while(1)
{
int data =rand( )%1234;
sem_wait(&blank_sem);//空格子减1
ring[step]=data;// 放入数据
sem_post(&data_sem);//格子中含有数据的个数加1
printf("The product done:%d\n",data);
step++;
step%=SIZE;
sleep(1);
}
}
void *consume( void *arg)
{
int step=0;
while(1)
{
int data=-1;//消费失败就会输出-1
sem_wait(&data_sem);//格子中含有数据的个数减1
data=ring[step];//拿出数据
sem_post(&blank_sem);//空格子数加1
printf("The consume done:%d\n",data);
step++;
step%=SIZE;
}
}
int main( )
{
pthread_t p;
pthread_t c;
pthread_create(&p,NULL,product,NULL);
pthread_create(&c,NULL,consume,NULL);
sem_init(&blank_sem,0,SIZE);//初始化信号量,刚开始的空格子数为SIZE大小
sem_init(&data_sem,0,0);//初始化信号量,刚开始的格子中含有数据的个数为0
pthread_join(p,NULL);
pthread_join(c,NULL);
sem_destroy(&blank_sem);//销毁信号量
sem_destroy(&data_sem);
return 0;
}
(3)运行成功后,就可以看到如图结果
以上是基于环形队列的单生产者单消费者模型,接下来我们来看看基于环形队列的多生产者多消费者模型。
基于环形队列的多生产者多消费者模型是在单生产者单消费者模型的基础上增加了消费者与生产者。要实现这一个模型我们只需加入互斥锁实现生产者与生产者之间的互斥,消费者与消费者之间的互斥。具体代码如下(以三消费者,三生产者为例):
#include<stdio.h>
#include<pthread.h>
#include<semaphore.h>
#define SIZE 64
#define CONSUMER 3//定义生产者的个数
#define PRODUCTER 3//定义消费者的个数
int ring[SIZE];
pthread_mutex_t lock1= PTHREAD_MUTEX_INITIALIZER;//用于完成生产者与生产者之间的互斥
pthread_mutex_t lock2= PTHREAD_MUTEX_INITIALIZER;//用于完成消费者与消费者之间的互斥
sem_t blank_sem;
sem_t data_sem;
void *product( void *arg)
{
int data=0;
static int step=0;
while(1)
{
pthread_mutex_lock(&lock1);//加锁
int data =rand( )%1234;
sem_wait(&blank_sem);//p操作
ring[step]=data;//产生数据
sem_post(&data_sem);//v操作
printf("The product done:%d\n",data);
step++;
step%=SIZE;
pthread_mutex_unlock(&lock1);//解锁
sleep(2);
}
}
void *consume( void *arg)
{
static int step=0;
while(1)
{
pthread_mutex_lock(&lock2);//加锁
int data=-1;
sem_wait(&data_sem);
data=ring[step];
sem_post(&blank_sem);
printf("The consume done:%d\n",data);
step++;
step%=SIZE;
pthread_mutex_unlock(&lock2);//解锁
sleep(2);
}
}
int main( )
{
pthread_t p[PRODUCTER];//创建生产者数组
pthread_t c[CONSUMER];//创建消费者数组
int i=0;
for(i=0;i<PRODUCTER;i++)//创建多生产者线程
{
pthread_create(&p[i],NULL,product,NULL);
}
for(i=0;i<CONSUMER;i++)//创建多消费者线程
{
pthread_create(&c[i],NULL,consume,NULL);
}
sem_init(&blank_sem,0,SIZE);
sem_init(&data_sem,0,0);
for( i=0;i<PRODUCTER;i++)//生产者线程等待
{
pthread_join(p[i],NULL);
}
for( i=0;i<CONSUMER;i++)//消费者线程等待
{
pthread_join(c[i],NULL);
}
sem_destroy(&blank_sem);
sem_destroy(&data_sem);
pthread_mutex_destroy(&lock1);
pthread_mutex_destroy(&lock2);
return 0;
}
代码运行成功后如下图所示: