一、概念
1. 生产者消费者模型可以被称为“三二一原则”。
“三”指的是三个原则,这三个原则指的是:
(1)生产者和消费者之间是互斥关系;
(2)生产者和消费者之间是同步和互斥关系;
( 3)消费者和消费者是互斥关系。
“二”指的是两种角色:生产者和消费者。
“一”指的是一个交易场所。
2.互斥锁
互斥锁的功能就是完成互斥功能。
(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 );
(4)pthread_mutex_trylock函数
int pthread_mutex_trylock(pthread_mutex_t *mutex );
一个线程可调用pthread_mutex_lock获得Mutex,如果另一个线程调用该函数获得了Mutex。则当前线程需要挂起等待,知道另一个线程释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。如果一个线程即想获得锁又不想被挂起等待,可以调用pthread_mutex_trylock函数尝试获得锁,如果Mutex已经被另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
3.条件变量
条件变量是用来描述临界资源状态的,它具有原子性。条件变量提供一种通知机制。
在我们的消费者消费的时候,如果单链表中一个结点都没有,那么消费就会失败,所以我们需要创建一个条件变量来表示消费者在什么情况下才能消费,如果条件变量不满足,那么线程就会进行等待,知道有人唤醒它为止。
(1)初始化条件变量
int pthread_cond_init(pthread_cond _t *cond,const pthread_cond _t *restrict attr);
它和Mutex的初始化和销毁类似,该函数初始化一个Condetion Variable,attr参数为NULL表示缺省属性
int pthread_cond_destroy(pthread_cond _t *cond);//销毁一个Condition Variable
如果条件变量是静态分配的,也可以用宏来定义PTHEAD_COND_INITIALIZER初始化。
(2)休眠
int pthread_cond_wait(pthread_cond _t *restrict cond,pthread_mutex_t *restrict mutex);
条件变量总是和一个Mutex搭配来使用的。一个线程可以调用该函数在条件变量上阻塞等待,这个函数做了以下三部操作:释放Mutex;阻塞等待;当被唤醒时,重新获得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;//消费失败则输出
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;
}
运行结果:
三、环形队列的生产者消费者模型
根据上图所示,每个格子就相当于超市货架,当每个货架为空时,生产者才能生产,当每个货架有东西时,消费者才能消费,且生产者与消费者必须同步,生产者必须快于消费者。
要符合环形队列的生产者消费者模型,生产者和消费者必须满足一下三个条件:
(1)生产者必须快于消费者
(2)生产者不能将消费者套圈
(3)消费者不能消费时,生产者先走;生产者满时,不能生产,消费者先走。
Semsphore信号量:Semaphore变量的类型为sem_t
初始化信号量:int sem_init(sem_t* sem,int pshared,unsigned int value)
value参数表示可用资源的数量,pshared参数为0表示信号量用于同一进程的线程。
用完Semaphore变量之后应该调用sem_destroy()释放与
Semaphore
相关的资源。
用sem_wait()可以获得资源(P操作),使semphore的值减1,如果调用semphore的值已经是0,则挂起等待。如果不希望挂起等待,可以调用sem_trywait()。
调用sem_post()可以释放资源(V操作),使semphore的值加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);//空格子减
ring[step] = data;// 放入数据
sem_post(&data_sem);//格子中含有数据的个数加
printf("The product done:%d\n", data);
step++;
step %= SIZE;
sleep(1);
}
}
void *consume(void *arg)
{
int step = 0;
while (1)
{
int data = -1;//消费失败就会输出
sem_wait(&data_sem);//格子中含有数据的个数减
data = ring[step];//拿出数据
sem_post(&blank_sem);//空格子数加
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);//初始化信号量,刚开始的格子中含有数据的个数为
pthread_join(p, NULL);
pthread_join(c, NULL);
sem_destroy(&blank_sem);//销毁信号量
sem_destroy(&data_sem);
return 0;
}
运行结果:
下来看一下基于环形队列的多生产多消费模型。
基于环形队列的多生产多消费者模型是在单生产单消费模型的基础上增加了消费者与生产者。要实现这个模型我们只需要加入互斥锁实现生产者与生产者之间的互斥,消费者与消费者之间的互斥。
下来我们看一下代码:
#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;
}
运行结果: