哲学家用餐问题的解决

实验报告

实验名称:哲学家用餐问题的解决

实验要求:

  1. 利用AND型信号量解决哲学家进餐问题。
  2. 临界区互斥编程原理。

实验内容:

1. 实现原理

哲学家用餐问题是一个经典的并发编程问题,旨在展示在共享资源和并发操作中可能发生的死锁和竞态条件。问题描述了五位哲学家坐在一张圆桌前,每位哲学家面前放有一个碗,而相邻的两个碗之间放有一只筷子,形成了一共五只筷子。哲学家交替地思考和进餐,但只有同时拿到左右两只筷子时才能进餐。

解决这个问题的关键是确保哲学家能够安全地进餐,避免死锁和竞态条件。以下是解决方案的核心思想:

  1. 互斥性(Mutual Exclusion): 保证同一时刻只有一个哲学家能够拿起或放下筷子。这可以通过互斥锁(mutex)来实现。在代码中,sem_wait(&mutex)表示进入临界区,sem_post(&mutex)表示退出临界区。

  2. 占有和等待(Hold and Wait): 确保哲学家在尝试获取第二只筷子之前,释放已经获取的筷子,防止死锁。这也是通过互斥锁实现的。

  3. 不剥夺(No Preemption): 一旦哲学家拿到一只筷子,其他哲学家不能夺取,只能等待其主动释放。

  4. 环路等待(Circular Wait): 通过给资源(筷子)编号,按一定顺序获取资源,避免形成循环等待。在这里,按照编号从小到大的顺序获取筷子。

  5. AND型信号量: 哲学家需要同时获取左右两只筷子才能进餐,这可以通过AND型信号量来实现。sem_wait(&mutex)sem_post(&mutex)用于保证对共享资源(筷子)的互斥访问,而在取筷子和放筷子的操作中,通过信号量的等待和释放来控制。

  6. 打印状态: 在代码中,通过一个单独的线程周期性地打印哲学家的状态和筷子的状态,以便观察程序的执行情况。

在整个解决方案中,互斥锁确保了对共享资源的互斥访问,信号量用于同步哲学家的行为,而按照一定规则获取和释放筷子,避免了死锁和竞态条件的发生。

然而,这个问题仍然是一个理论上的问题,实际应用中可能会有更复杂的场景和需求,需要更为细致的设计和实现。

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); // 等待一段时间再重新打印

    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值