实验题目

生产者—消费者问题
在第 5.7.1 节中,我们提出了一种使用有界缓冲区的信号量解决方案来解决生产者消费者问题。你将使用图 5.9 和 5.10 中所示的生产者和消费者进程来设计有界缓冲区问题的编程解决方案。5.7.1 节中介绍的解决方案使用三个信号量:empty 和 full,它们计算缓冲区中空槽和满槽的数量,以及 mutex,它是一个二进制(或互斥)信号量,用于保护实际插入或删除缓冲区中的项目。你将使用标准计数信号量表示empty和full,并使用互斥锁而不是二进制信号量来表示mutex。生产者和消费者作为单独的线程运行——将产品移入和移出与empty、full和mutex结构同步的缓冲区。可以使用 Pthreads 或 Windows API 实现。
生产者线程将在随机休眠一段时间和将随机整数插入缓冲区之间交替。随机数将使用 rand() 函数产生,该函数产生介于 O 和 RAND_MAX 之间的随机整数。消费者也会随机休眠一段时间,醒来后会尝试从缓冲区中删除一个项目。生产者和消费者线程的概要如图 5.26 所示。
如前所述,您可以使用 Pthreads 或 Windows API 解决此问题。在以下部分中,我们将提供有关每个选择的更多信息。

相关原理与知识

  1. 生产者-消费者问题
    生产者-消费者问题是经典的同步问题,涉及多个线程之间的协调。具体来说:
    • 生产者:负责生成数据并将数据放入缓冲区。
    • 消费者:负责从缓冲区中取出数据并进行处理。
    • 缓冲区:一个有限大小的存储空间,用于临时存放生产者生成的数据。
    为了解决生产者和消费者之间的冲突,需要使用同步机制确保以下条件:
    1. 缓冲区不能被同时写入和读取。
    2. 缓冲区不能溢出(生产者不能向已满的缓冲区写入数据)。
      3.缓冲区不能为空时被读取(消费者不能从空的缓冲区读取数据)。
  2. 信号量
    信号量是一种同步工具,用于控制对共享资源的访问。在本实验中,使用了三种信号量:
    • empty:表示缓冲区中空闲槽的数量。初始值为缓冲区大小。
    • full:表示缓冲区中已占用槽的数量。初始值为 0。
    • mutex:用于保护缓冲区的互斥访问。使用互斥锁代替二进制信号量

实验过程

  1. 定义全局变量:
    • 缓冲区 buffer 是一个固定大小的数组,用于存储生产者生成的数据。
    • 变量 count 表示当前缓冲区中的项目数量。
  2. 初始化信号量和互斥锁:
    • empty 初始化为缓冲区大小,表示所有槽位都是空的。
    • full 初始化为 0,表示没有槽位被占用。
    • 使用 pthread_mutex_init 初始化互斥锁 mutex。
  3. 实现生产者函数:
    • 生产者线程生成随机整数,并将其插入缓冲区。
    • 在插入之前,生产者需要等待 empty 信号量以确保缓冲区中有空槽。
    • 使用互斥锁保护对缓冲区的访问。
    • 插入完成后,增加 full 信号量以通知消费者缓冲区中有新数据。

  4. 实现消费者函数:
    • 消费者线程从缓冲区中删除项目。
    • 在删除之前,消费者需要等待 full 信号量以确保缓冲区中有数据可消费。
    • 使用互斥锁保护对缓冲区的访问。
    • 删除完成后,增加 empty 信号量以通知生产者缓冲区中有空槽可用。

  5. 实现主函数
  • 创建线程:
    • 使用 pthread_create 创建生产者和消费者线程。
    • 主线程通过 pthread_join 等待子线程结束(实际运行时可通过 Ctrl+C 中止)。
  • 销毁资源:
    • 销毁信号量和互斥锁以释放资源。

实验结果与分析

关键运行结果分析

  1. 生产者行为:
    • 生产者线程生成随机整数,并将其插入缓冲区。
    • 当缓冲区满时,生产者会等待 empty 信号量,直到有空槽可用。
  2. 消费者行为:
    • 消费者线程从缓冲区中删除项目。
    • 当缓冲区为空时,消费者会等待 full 信号量,直到有数据可消费。
  3. 同步机制的作用:
    • empty 和 full 信号量确保生产者和消费者不会违反缓冲区的容量限制。
    • mutex 互斥锁确保缓冲区的访问是线程安全的,避免数据竞争。

问题总结

遇到的问题与挑战:

  1. 信号量的初始化:
    • 初始值设置不正确可能导致生产者或消费者无法正常工作。例如,empty 应初始化为缓冲区大小,而 full 应初始化为 0。
  2. 随机数生成:
    • 如果未正确初始化随机数种子(如未调用 srand(time(NULL))),生成的随机数可能是固定的。
  3. 线程退出:
    • 主线程无法直接等待子线程结束,因为子线程是无限循环的。因此,实验中通过按 Ctrl+C 中止程序。
    解决方法
  4. 检查信号量初始值:
    • 确保 empty 和 full 的初始值符合缓冲区的状态。
  5. 初始化随机数种子:
    • 使用 srand(time(NULL)) 初始化随机数种子,确保每次运行生成不同的随机数。
  6. 优雅退出:
    • 可以通过设置标志变量的方式让线程在特定条件下退出,从而避免强制终止程序。

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

// 定义缓冲区大小
#define BUFFER_SIZE 5

// 全局变量
int buffer[BUFFER_SIZE]; // 缓冲区
int count = 0; // 当前缓冲区中的项目数量
sem_t empty, full; // 信号量:empty 和 full
pthread_mutex_t mutex; // 互斥锁

// 生产者函数
void* producer(void* arg) {
int item;
while (1) {
// 生成随机整数
item = rand() % 100;

// 随机休眠一段时间
sleep(rand() % 3);

// 获取空槽信号量
sem_wait(&empty);

// 加锁,保护缓冲区访问
pthread_mutex_lock(&mutex);

// 插入项目到缓冲区
buffer[count] = item;
printf("生产者插入项目: %d\n", item);
count++;

// 解锁
pthread_mutex_unlock(&mutex);

// 增加满槽信号量
sem_post(&full);
}
}

// 消费者函数
void* consumer(void* arg) {
int item;
while (1) {
// 随机休眠一段时间
sleep(rand() % 3);

// 获取满槽信号量
sem_wait(&full);

// 加锁,保护缓冲区访问
pthread_mutex_lock(&mutex);

// 从缓冲区中删除项目
count--;
item = buffer[count];
printf("消费者消费项目: %d\n", item);

// 解锁
pthread_mutex_unlock(&mutex);

// 增加空槽信号量
sem_post(&empty);
}
}

int main() {
pthread_t prod_thread, cons_thread;

// 初始化信号量和互斥锁
sem_init(&empty, 0, BUFFER_SIZE); // 初始化 empty 信号量为缓冲区大小
sem_init(&full, 0, 0); // 初始化 full 信号量为 0
pthread_mutex_init(&mutex, NULL);

// 初始化随机数种子
srand(time(NULL));

// 创建生产者和消费者线程
pthread_create(&prod_thread, NULL, producer, NULL);
pthread_create(&cons_thread, NULL, consumer, NULL);

// 等待线程结束(实际运行时可按 Ctrl+C 中止)
pthread_join(prod_thread, NULL);
pthread_join(cons_thread, NULL);

// 销毁信号量和互斥锁
sem_destroy(&empty);
sem_destroy(&full);
pthread_mutex_destroy(&mutex);

return 0;
}