什么是生产者-消费者模型?
生产者-消费者是非常著名的一个问题。它的描述是:有一群生产者进程在生产产品,并将这些产品提供给消费者进程去消费,为使生产者与消费者能并发的执行,在两者之间设置了具有n个缓冲区的缓冲池,生产者进程将其所生产的产品放入一个缓冲区中;消费者进程可从一个缓冲区中取走产品。他们之间必须保持同步,即:不允许消费者到一个空的缓冲区中去取产品,也不允许生产者向一个已经装满产品的缓冲区中投放产品。当缓冲区为空时,消费者进程需要挂起休眠,直至生产者进程将产品放入缓冲区,消费者才能被唤醒;相反,如果缓冲区满时,消费者进程需要挂起,直至缓冲区中的产品被消费者拿走,才可以被唤醒。
举一个例子:我们把缓冲区比作平时购物的超市,生产者相当于是供货商,消费者就是去超市买东西的我们。
- 当超市的货物架上没有东西卖时,我们需要等到供货商送来货物才可以去买,超市的货物架上放满了货物,只有等到我们拿走了,才能继续上货,这是生产者和消费者之间的同步;
- 我们只有等超市摆好货物才能过去买,相反,超市也只能在我们把货物拿完才可以放;这是生产者与消费者之间的互斥(虽然在生活中这个例子不太恰当,但是在计算机中确实这样做的);
- 假如超市中的某个产品中有一件,但是可能很多消费者都想要,这是消费者之间的竞争;
- 假如某个地区只有一个城市,那么可能会有很多生产者想把他们的产品放到超市卖,这是生产者之间的竞争。
所以,总结以上所述:
- 三种关系:
- 消费者和生产者:互斥与同步
- 消费者和消费者:互斥(竞争)
- 生产者和生产者:互斥(竞争)
- 两种角色:消费者、生产者
- 一个交易场所:具有存储数据的缓冲区
我们在Linux环境下,使用线程来模拟消费者和生产者。利用链表的插入操作模拟产生者生产,链表的删除操作来模拟消费者消费产品。
关于互斥量及条件变量的使用,可以参考之前的博客。
具体实现代码如下:
#include
#include.h>
#include.h>
#include
#includepthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//互斥量typedef struct node//链表结点
{
int data;
struct node* next;
}node, *node_p, **node_pp;
node_p getnode(int data)//创建结点
{
node_p ret = malloc(sizeof(node));
if(ret != NULL)
{
ret->data = https://www.it610.com/article/data;
ret->next = NULL;
return ret;
}
return NULL;
}void initlist(node_pp pphead)//初始化链表——创建头结点
{
if(pphead == NULL)
exit(1);
*pphead = malloc(sizeof(node));
(*pphead)->next = NULL;
}int isEmpty(node_p phead)//链表判空,空返回1
{
return (phead->next == NULL)?1:0;
}void push(node_p phead, int data)//链表的头插
{
node_p tmp = getnode(data);
if(tmp != NULL)
{
tmp->next = phead->next;
phead->next = tmp;
}
}void print(node_p phead)//打印链表结点的数据
{
phead = phead->next;
while(phead)
{
printf("%d", phead->data);
phead = phead->next;
}
}void erase(node_p phead, int *x)//删除链表的数据
{if(isEmpty(phead))
return;
node_p cur = phead->next;
phead->next = cur->next;
*x = cur->data;
free(cur);
}void destroy(node_p phead)//销毁链表
{
while(!isEmpty(phead))
{
int data;
erase(phead, &data);
}
free(phead);
}void *producer(void* arg)//生产者线程执行的函数
{
node_p phead = (node_p)arg;
while(1)
{
int data = https://www.it610.com/article/rand()%1000;
pthread_mutex_lock(&mutex);
push(phead, data);
printf("producer done, %d\n", data);
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
sleep(1);
}
}void* consumer(void* arg)//消费者线程执行的函数
{
node_p phead = (node_p)arg;
int data = https://www.it610.com/article/0;
while(1)
{
pthread_mutex_lock(&mutex);
while(isEmpty(phead))
{
printf("no product, please wait...\n");
pthread_cond_wait(&cond, &mutex);
}
erase(phead, &data);
printf("consumer done, %d\n", data);
pthread_mutex_unlock(&mutex);
usleep(100000);
}
}
int main()
{
node_p phead;
initlist(&phead);
srand((unsigned long)time(NULL));
pthread_t t1, t2;
pthread_create(&t1, NULL, producer, (void*)phead);
//生产者线程
pthread_create(&t2, NULL, consumer, (void*)phead);
//消费者线程pthread_join(t1, NULL);
//线程等待
pthread_join(t2, NULL);
destroy(phead);
//释放链表
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
运行结果:
文章图片
基于环形队列,使用POSIX信号量实现生产者-消费者
同样,我们在Linux环境下,使用线程模拟生产者和消费者。
首先,介绍一下POSIX信号量,它和System V信号量作用相同,达到无冲突的访问共享资源的目的,但POSIX可以用于线程间同步。
1. 初始化信号量
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
- pshared:0表示线程间共享,非0表示进程间共享
- value:信号量的初始值
int sem_destroy(sem_t *sem);
3. 等待信号量
等待信号量,会将信号量的值减1,相当于P操作
int sem_wait(sem_t *sem);
【Linux|【Linux】生产者消费者模型】4. 发布信号量
发布信号量,表示资源使用完毕。将信号量值加1,相当于V操作
int sem_post(sem_t *sem);
环形队列
文章图片
具体实现代码:
#include
#include
#include
#include#include
#include
#include#define SIZE 10
sem_t sem_blank;
sem_t sem_data;
int ring[SIZE];
void* produce(void* arg)
{
int i = 0;
while(1)
{
sem_wait(&sem_blank);
int data = https://www.it610.com/article/rand()%1234;
ring[i] = data;
i++;
i %= SIZE;
printf("producer done...data:%d\n", data);
sem_post(&sem_data);
sleep(1);
}
}void* consume(void* arg)
{
int i = 0;
while(1)
{
sem_wait(&sem_data);
int data = https://www.it610.com/article/ring[i];
i++;
i %= SIZE;
printf("consumemer done...data:%d\n", data);
sem_post(&sem_blank);
usleep(1000);
}
}
int main()
{
srand((unsigned long)time(NULL));
pthread_t t1, t2;
sem_init(&sem_blank, 0, SIZE);
sem_init(&sem_data, 0, 0);
pthread_create(&t1, NULL, produce, NULL);
pthread_create(&t2, NULL, consume, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
sem_destroy(&sem_blank);
sem_destroy(&sem_data);
return 0;
}
运行结果:
文章图片
推荐阅读
- Linux|Linux下生产者与消费者模型
- Linux|【Linux】—— 基于信号量的生产者消费者模型
- Linux|Linux生产者消费者模型
- 程序员宝贵经验集|代码调不通,OJ题不会做(寻求帮助,学会提问-《提问的智慧》)
- 操作系统|实验一 Linux基本操作
- Linux|Linux基本操作
- 校招|校招 --阶段一 系统编程】基于进程控制的实现简单的shell