用队列实现生产者-消费者模型 —— 详解与代码讲解
一、引言
生产者-消费者问题(Producer-Consumer Problem)是操作系统、并发编程和数据结构课程中的经典案例。它描述了两个角色:生产者负责生产数据并放入缓冲区,消费者则从缓冲区取出数据进行消费。两者通过一个共享的缓冲区(通常为队列)进行协作,既要保证数据的正确流转,又要避免资源竞争和数据丢失。
本篇文章将以循环队列为核心,详细讲解如何用 C 语言实现一个简洁、易懂的生产者-消费者模型。本文适合数据结构初学者和对并发编程感兴趣的读者。
二、生产者-消费者问题的基本思想
1. 问题描述
- 生产者不断产生产品,放入缓冲区。
- 消费者不断从缓冲区取出产品进行消费。
- 缓冲区有容量限制,满时生产者需等待,空时消费者需等待。
2. 解决思路
- 用一个队列作为缓冲区,保证产品先进先出(FIFO)。
- 生产者在队列未满时入队,消费者在队列非空时出队。
- 用队列的计数器和指针管理队列状态。
三、循环队列的数据结构设计
1. 队列结构体定义
循环队列是一种高效利用数组空间的队列实现方式。其结构体如下:
#define QUEUE_MAX_SIZE 10
typedef struct
{
int data[QUEUE_MAX_SIZE]; // 存储产品的数组
int front; // 队头指针,指向下一个要被消费的元素
int rear; // 队尾指针,指向下一个要被生产的位置
int count; // 当前队列元素个数
} Queue;
字段说明
data:用于存储产品(这里用整数模拟产品)。front:队头指针,指向下一个要被消费的元素。rear:队尾指针,指向下一个要被生产的位置。count:当前队列中的产品数量。
2. 队列操作函数声明
void queue_init(Queue *q);
int queue_is_empty(Queue *q);
int queue_is_full(Queue *q);
int queue_enqueue(Queue *q, int item);
int queue_dequeue(Queue *q, int *item);
void queue_print(Queue *q);
四、队列操作函数实现
1. 初始化队列
void queue_init(Queue *q)
{
q->front = 0;
q->rear = 0;
q->count = 0;
}
2. 判断队列是否为空
int queue_is_empty(Queue *q)
{
return q->count == 0;
}
3. 判断队列是否已满
int queue_is_full(Queue *q)
{
return q->count == QUEUE_MAX_SIZE;
}
4. 入队操作(生产者调用)
int queue_enqueue(Queue *q, int item)
{
if (queue_is_full(q))
return 0;
q->data[q->rear] = item;
q->rear = (q->rear + 1) % QUEUE_MAX_SIZE;
q->count++;
return 1;
}
5. 出队操作(消费者调用)
int queue_dequeue(Queue *q, int *item)
{
if (queue_is_empty(q))
return 0;
*item = q->data[q->front];
q->front = (q->front + 1) % QUEUE_MAX_SIZE;
q->count--;
return 1;
}
6. 打印队列内容
void queue_print(Queue *q)
{
printf("Queue: [");
for (int i = 0, idx = q->front; i < q->count; i++, idx = (idx + 1) % QUEUE_MAX_SIZE)
{
printf("%d ", q->data[idx]);
}
printf("] (count=%d)\n", q->count);
}
五、生产者与消费者函数实现
1. 生产者函数
void producer(Queue *q)
{
if (queue_is_full(q))
{
printf("Queue is full, cannot produce.\n");
return;
}
int data = rand() % 100; // 生成一个随机数作为产品
queue_enqueue(q, data);
printf("Produced: %d\n", data);
}
- 首先判断队列是否已满,满则不能生产。
- 随机生成一个产品并入队。
2. 消费者函数
void consumer(Queue *q)
{
if (queue_is_empty(q))
{
printf("Queue is empty, cannot consume.\n");
return;
}
int data;
queue_dequeue(q, &data);
printf("Consumed: %d\n", data);
}
- 首先判断队列是否为空,空则不能消费。
- 从队列中取出产品并打印。
六、主函数与交互流程
主函数通过命令行交互,模拟生产和消费过程:
int main(void)
{
Queue q;
queue_init(&q);
char cmd;
printf("Simple Producer-Consumer Simulation (Queue Version)\n");
printf("p: produce, c: consume, q: quit\n");
while (1)
{
queue_print(&q);
printf("Enter command (p/c/q): ");
scanf(" %c", &cmd);
if (cmd == 'p')
{
producer(&q);
}
else if (cmd == 'c')
{
consumer(&q);
}
else if (cmd == 'q')
{
break;
}
else
{
printf("Invalid command.\n");
}
}
return 0;
}
- 用户输入
p生产产品,c消费产品,q退出程序。 - 每次操作后都会打印当前队列状态,便于观察缓冲区变化。
七、完整代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define QUEUE_MAX_SIZE 10
typedef struct
{
int data[QUEUE_MAX_SIZE];
int front;
int rear;
int count;
} Queue;
void queue_init(Queue *q)
{
q->front = 0;
q->rear = 0;
q->count = 0;
}
int queue_is_empty(Queue *q)
{
return q->count == 0;
}
int queue_is_full(Queue *q)
{
return q->count == QUEUE_MAX_SIZE;
}
int queue_enqueue(Queue *q, int item)
{
if (queue_is_full(q))
return 0;
q->data[q->rear] = item;
q->rear = (q->rear + 1) % QUEUE_MAX_SIZE;
q->count++;
return 1;
}
int queue_dequeue(Queue *q, int *item)
{
if (queue_is_empty(q))
return 0;
*item = q->data[q->front];
q->front = (q->front + 1) % QUEUE_MAX_SIZE;
q->count--;
return 1;
}
void queue_print(Queue *q)
{
printf("Queue: [");
for (int i = 0, idx = q->front; i < q->count; i++, idx = (idx + 1) % QUEUE_MAX_SIZE)
{
printf("%d ", q->data[idx]);
}
printf("] (count=%d)\n", q->count);
}
void producer(Queue *q)
{
if (queue_is_full(q))
{
printf("Queue is full, cannot produce.\n");
return;
}
int data = rand() % 100; // 生成一个随机数作为产品
queue_enqueue(q, data);
printf("Produced: %d\n", data);
}
void consumer(Queue *q)
{
if (queue_is_empty(q))
{
printf("Queue is empty, cannot consume.\n");
return;
}
int data;
queue_dequeue(q, &data);
printf("Consumed: %d\n", data);
}
int main(void)
{
Queue q;
queue_init(&q);
char cmd;
printf("Simple Producer-Consumer Simulation (Queue Version)\n");
printf("p: produce, c: consume, q: quit\n");
while (1)
{
queue_print(&q);
printf("Enter command (p/c/q): ");
scanf(" %c", &cmd);
if (cmd == 'p')
{
producer(&q);
}
else if (cmd == 'c')
{
consumer(&q);
}
else if (cmd == 'q')
{
break;
}
else
{
printf("Invalid command.\n");
}
}
return 0;
}
八、运行方法
1. 保存代码
将上述代码保存为 produce_consumer.c 文件。
2. 编译与运行
Windows 下
gcc produce_consumer.c -o produce_consumer
produce_consumer.exe
Linux/macOS 下
gcc produce_consumer.c -o produce_consumer
./produce_consumer
3. 交互示例
Simple Producer-Consumer Simulation (Queue Version)
p: produce, c: consume, q: quit
Queue: [] (count=0)
Enter command (p/c/q): p
Produced: 42
Queue: [42 ] (count=1)
Enter command (p/c/q): p
Produced: 17
Queue: [42 17 ] (count=2)
Enter command (p/c/q): c
Consumed: 42
Queue: [17 ] (count=1)
Enter command (p/c/q): q
九、模型分析与扩展
1. 队列的优势
- 先进先出(FIFO):保证产品按生产顺序被消费。
- 空间复用:循环队列充分利用数组空间,避免“假溢出”。
- 易于扩展:可方便地扩展为多线程并发模型。
2. 适用场景
- 单线程模拟生产者-消费者问题。
- 数据流缓冲、任务调度等需要先进先出特性的场景。
3. 多线程扩展建议
- 若要实现多线程并发,需要引入互斥锁(mutex)和条件变量(condition variable)等同步机制,防止竞态条件和数据不一致。
- 可参考 pthread 库的相关用法。
十、总结
本文详细介绍了如何用循环队列实现生产者-消费者模型,涵盖了队列的定义、操作函数、生产者与消费者实现、主函数交互流程以及完整代码和运行方法。通过本例,你可以深入理解队列数据结构的实际应用,以及生产者-消费者问题的基本思想。
如需进一步学习多线程并发、信号量等高级内容,欢迎继续关注相关专题!

470

被折叠的 条评论
为什么被折叠?



