实验报告
实验名称:哲学家用餐问题的解决
实验要求:
- 利用AND型信号量解决哲学家进餐问题。
- 临界区互斥编程原理。
实验内容:
1. 实现原理
哲学家用餐问题是一个经典的并发编程问题,旨在展示在共享资源和并发操作中可能发生的死锁和竞态条件。问题描述了五位哲学家坐在一张圆桌前,每位哲学家面前放有一个碗,而相邻的两个碗之间放有一只筷子,形成了一共五只筷子。哲学家交替地思考和进餐,但只有同时拿到左右两只筷子时才能进餐。
解决这个问题的关键是确保哲学家能够安全地进餐,避免死锁和竞态条件。以下是解决方案的核心思想:
-
互斥性(Mutual Exclusion): 保证同一时刻只有一个哲学家能够拿起或放下筷子。这可以通过互斥锁(mutex)来实现。在代码中,
sem_wait(&mutex)
表示进入临界区,sem_post(&mutex)
表示退出临界区。 -
占有和等待(Hold and Wait): 确保哲学家在尝试获取第二只筷子之前,释放已经获取的筷子,防止死锁。这也是通过互斥锁实现的。
-
不剥夺(No Preemption): 一旦哲学家拿到一只筷子,其他哲学家不能夺取,只能等待其主动释放。
-
环路等待(Circular Wait): 通过给资源(筷子)编号,按一定顺序获取资源,避免形成循环等待。在这里,按照编号从小到大的顺序获取筷子。
-
AND型信号量: 哲学家需要同时获取左右两只筷子才能进餐,这可以通过AND型信号量来实现。
sem_wait(&mutex)
和sem_post(&mutex)
用于保证对共享资源(筷子)的互斥访问,而在取筷子和放筷子的操作中,通过信号量的等待和释放来控制。 -
打印状态: 在代码中,通过一个单独的线程周期性地打印哲学家的状态和筷子的状态,以便观察程序的执行情况。
在整个解决方案中,互斥锁确保了对共享资源的互斥访问,信号量用于同步哲学家的行为,而按照一定规则获取和释放筷子,避免了死锁和竞态条件的发生。
然而,这个问题仍然是一个理论上的问题,实际应用中可能会有更复杂的场景和需求,需要更为细致的设计和实现。
2. 程序结构(流程图)
2.1 主程序模块流程图:
[初始化]
|
[V]
|
[创建哲学家线程]
|
[等待线程结束]
|
[退出程序]
2.2 状态改变模块流程图:
[等待状态]
|
[V]
|
[获取两只筷子]
|
[V]
|
[进餐状态]
|
[V]
|
[放下筷子]
|
[V]
|
[思考状态]
2.3 返回哲学家状态流程图:
[获取哲学家状态]
|
[V]
|
[返回状态]
2.4 返回筷子状态模块流程图:
[获取筷子状态]
|
[V]
|
[返回状态]
3. 数据结构
3.1 定义一个哲学家类:
typedef struct {
int number; // 哲学家的编号
int status; // 0表示等待,1表示吃饭,2表示思考
} Philosopher;
3.2 定义函数:
int Number(const Philosopher *philosopher); // 返回哲学家的编号
int Status(const Philosopher *philosopher); // 返回哲学家当前状态
void Philosopher(int num); // 哲学家类构造函数
int find(const Philosopher *philosopher); // 返回该哲学家编号
int getinfo(const Philosopher *philosopher); // 返回哲学家当前状态
void Change(Philosopher *philosopher); // 根据题目要求改变哲学家的状态
3.3 公有对象
bool tools[6]; // 用于保存筷子的状态,true表示空闲,false表示被使用
3.4 公有函数:
void print(const Philosopher *philosopher); // 返回一个哲学家的状态
void toolstatus(bool tools[6]); // 返回一个筷子的状态
4. 实现步骤
4.1 打开VC,创建工程
在VC中选择菜单项File->New,选择Projects选项卡并建立一个名为xwj的win32 console application工程。
4.2 创建源文件xwj.cpp
选择菜单项Project->Add to project->Files,在弹出的窗口中输入文件名xwj.cpp,并回答“yes”以创建新文件。通过Workspace->Source Files打开该文件,编辑源文件并保存。
4.3 编译连接
通过调用菜单项Build->Rebuild all进行编译连接,得到debug->xwj.exe程序。
5. 结果分析和实验心得
在完成实验后,我们成功地解决了哲学家进餐问题,使用AND型信号量和临界区互斥编程原理确保了程序的正确性。通过这个实验,我深入理解了并发编程中的同步和互斥概念,以及如何使用信号量和互斥锁来处理共享资源。
总结:
这次实验让我更好地理解了操作系统中的同步和互斥问题,同时提高了我的C语言编程技能。通过实践,我掌握了使用信号量和互斥锁解决并发问题的基本方法。这对我的学习和未来的工作都有很大的帮助。
附代码:
#include <stdio.h>
#include <stdbool.h>
#include <pthread.h>
#include <semaphore.h>
#define NUM_PHILOSOPHERS 5
typedef struct {
int number; // 哲学家的编号
int status; // 0表示等待,1表示吃饭,2表示思考
} Philosopher;
Philosopher philosophers[NUM_PHILOSOPHERS];
bool tools[6]; // true表示筷子空闲,false表示被使用
sem_t mutex; // 互斥锁
// 函数声明
void init();
void *philosopher(void *arg);
void takeChopsticks(int philosopherNumber);
void putChopsticks(int philosopherNumber);
void *print(void *arg);
void *toolStatus(void *arg);
// 主程序
int main() {
// 初始化
init();
// 创建哲学家线程
pthread_t philosopherThreads[NUM_PHILOSOPHERS];
for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {
pthread_create(&philosopherThreads[i], NULL, philosopher, (void *)&philosophers[i]);
}
// 创建打印线程
pthread_t printThread;
pthread_create(&printThread, NULL, print, NULL);
// 创建筷子状态监控线程
pthread_t toolStatusThread;
pthread_create(&toolStatusThread, NULL, toolStatus, NULL);
// 等待哲学家线程结束
for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {
pthread_join(philosopherThreads[i], NULL);
}
// 等待其他线程结束
pthread_join(printThread, NULL);
pthread_join(toolStatusThread, NULL);
return 0;
}
// 初始化函数
void init() {
sem_init(&mutex, 0, 1);
// 初始化哲学家和筷子状态
for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {
philosophers[i].number = i;
philosophers[i].status = 0; // 初始状态为等待
tools[i] = true; // 初始时所有筷子都空闲
}
tools[NUM_PHILOSOPHERS] = true; // 第6把筷子
}
// 哲学家行为函数
void *philosopher(void *arg) {
Philosopher *philosopher = (Philosopher *)arg;
while (1) {
// 等待状态
sem_wait(&mutex);
philosopher->status = 0;
sem_post(&mutex);
// 获取两只筷子
takeChopsticks(philosopher->number);
// 进餐状态
sem_wait(&mutex);
philosopher->status = 1;
sem_post(&mutex);
// 放下筷子
putChopsticks(philosopher->number);
// 思考状态
sem_wait(&mutex);
philosopher->status = 2;
sem_post(&mutex);
}
return NULL;
}
// 获取两只筷子
void takeChopsticks(int philosopherNumber) {
sem_wait(&mutex);
// 左右两只筷子的编号
int leftChopstick = philosopherNumber;
int rightChopstick = (philosopherNumber + 1) % NUM_PHILOSOPHERS;
// 等待筷子空闲
while (!tools[leftChopstick] || !tools[rightChopstick]) {
sem_post(&mutex);
usleep(100000); // 等待一段时间再重新尝试
sem_wait(&mutex);
}
// 占用筷子
tools[leftChopstick] = false;
tools[rightChopstick] = false;
sem_post(&mutex);
}
// 放下筷子
void putChopsticks(int philosopherNumber) {
sem_wait(&mutex);
// 左右两只筷子的编号
int leftChopstick = philosopherNumber;
int rightChopstick = (philosopherNumber + 1) % NUM_PHILOSOPHERS;
// 释放筷子
tools[leftChopstick] = true;
tools[rightChopstick] = true;
sem_post(&mutex);
}
// 打印哲学家状态
void *print(void *arg) {
while (1) {
for (int i = 0; i < NUM_PHILOSOPHERS; ++i) {
printf("Philosopher %d: %s\n", philosophers[i].number, philosophers[i].status == 0 ? "Waiting" : philosophers[i].status == 1 ? "Eating" : "Thinking");
}
printf("\n");
usleep(500000); // 等待一段时间再重新打印
}
}
// 打印筷子状态
void *toolStatus(void *arg) {
while (1) {
printf("Chopstick status: ");
for (int i = 0; i <= NUM_PHILOSOPHERS; ++i) {
printf("%s ", tools[i] ? "Free" : "In use");
}
printf("\n");
usleep(500000); // 等待一段时间再重新打印
}
}