用队列实现生产者-消费者模型 —— 详解与代码讲解

用队列实现生产者-消费者模型 —— 详解与代码讲解

一、引言

生产者-消费者问题(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 
#include 
#include 
#include 

#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 库的相关用法。

十、总结

本文详细介绍了如何用循环队列实现生产者-消费者模型,涵盖了队列的定义、操作函数、生产者与消费者实现、主函数交互流程以及完整代码和运行方法。通过本例,你可以深入理解队列数据结构的实际应用,以及生产者-消费者问题的基本思想。

如需进一步学习多线程并发、信号量等高级内容,欢迎继续关注相关专题!


你可能感兴趣的:(C语言项目,计算机网络,C,操作系统)