设计一 采用预先分配法预防死锁的哲学家就餐问题
1. 实验目的
理解死锁的概念,掌握死锁预防方法。
死锁是进程并发执行过程中可能出现的现象,哲学家就餐问题是描述死锁的经典例子。为了防止死锁,可以采用资源预先分配法。资源预先分配法是指进程在运行前一次性地向系统申请它所需要的全部资源,如果系统当前不能够满足进程的全部资源请求,则不分配资源, 此进程暂不投入运行,如果系统当前能够满足进程的全部资源请求, 则一次性地将所申请的资源全部分配给申请进程。在哲学家就餐问题中,要采用资源预先分配法只需让每个哲学家同时申请左右两根筷子。
2. 实验要求
利用多线程技术编写哲学家就餐程序,演示采用死锁防止方法后不产生死锁的情况。
3. 实验步骤
3.1 程序结构设计
程序需要六个线程,主线程用于显示功能描述;五个哲学家线程用于模拟哲学家的活动,即不停地思考、饥饿、进食。相邻的两个哲学家线程需要共享他们中间的同一根筷子,因此对每一根筷子的使用要互斥,用互斥体数组h_mutex_chopsticks来实现。主线程创建五个哲学家线程后要等待所有哲学家结束,用线程句柄数组h_thread来表示五个线程,主线程通过等待这五个线程句柄来实现同步。
该程序共有7个函数,这些函数可以分成4组。各组包含的函数及其功能如图4-1所示。
组别 |
包括函数 |
函数功能 |
一 |
main() |
显示主菜单,接收用户的选择并执行相应的功能。 |
二 |
deadlock_philosopher() deadlock() |
演示死锁情况的哲学家线程函数 初始化函数:创建五个哲学家并等待它们结束 |
三 |
ordered_allocation_philosopher() ordered_allocation() |
通过按序分配法防止死锁的哲学家线程函数 初始化函数:创建五个哲学家并等待它们结束 |
四 |
pre_allocation_philosopher() pre_allocation() |
通过预先分配法防止死锁的哲学家线程函数 初始化函数:创建五个哲学家并等待它们结束 |
图4-1 函数及其功能 |
3.2 算法设计
下面给出预分配法函数pre _allocation_philosopher和初始化函数pre_allocation的算法描述。
设计二 采用有序分配法预防死锁的哲学家就餐问题
1. 实验目的
理解死锁的概念,掌握死锁预防方法。
死锁是进程并发执行过程中可能出现的现象,哲学家就餐问题是描述死锁的经典例子。
为了防止死锁,可以采用资源有序分配法。资源有序分配法是指事先将所有资源类全排序, 即赋予每一个资源类一个唯一的整数,规定进程必需按照资源编号由小到大的次序申请资源。
在哲学家就餐问题中,要采用资源有序分配法只需规定每个哲学家先申请左右两根筷子中编号小的筷子,再申请编号大的筷子。
2. 实验要求
利用多线程技术编写哲学家就餐程序,演示采用死锁防止方法后不产生死锁的情况。
3.2 算法设计
设计三 不预防死锁情况下的哲学家就餐问题
1. 实验目的
理解死锁的概念。
死锁是进程并发执行过程中可能出现的现象,哲学家就餐问题是描述死锁的经典例子。假设有几位哲学家围坐在一张餐桌旁,桌上有吃不尽的食品,每两位哲学家之间摆放着一根筷子,筷子的个数与哲学家的数量相等,每一位哲学家要么思考,要么等待,要么拿起左右两根筷子进餐。本设计假设有五个哲学家和五根筷子,它们的编号都是从0到4。 如果每位哲学家都拿起左边的筷子,就会发生死锁。
2. 实验要求
利用多线程技术编写哲学家就餐程序,使之在运行时能演示产生死锁的情况。
3.2 算法设计
在windows中可以用系统调用WaitForMultipleObjects()同时申请两份资源,但是在linux中没有相应的系统调用,因此要在linux下实现资源预分配法,就要自己编写同时申请两根筷子的函数。这需要将哲学家的状态增至三个, 即思考、饥俄、进食,每个哲学家仅在饥俄时才申请筷子,而且同时申请其左右两根筷子,如果此时左右两根子不同时空闲,则哲学家将等待。具体解法如下所示。
#define N 5
typedef enum{thinking, hungry, eating}status;
status state[N];
semaphore self[N];
semaphore mutex = 1;
void test(int i)
{
if((state[i] == hungry)&&
(state[(i-1)%N] != eating)&&
(state[(i+1)%N] != eating)){
state[i] = eating;
V(self[i]);
}
}
void pick_chopsticks(int i)
{
P(mutex);
state[i] = hungry;
test(i);
V(mutex);
P(self[i]);
}
void put_chopsticks(int i)
{
P(mutex);
state[i] = thinking;
test((i-1)%N);
test((i+1)%N);
V(mutex);
}
void philosopher(int i)
{
while(1){
think();
pick_chopsticks(i);
eat();
put_chopsticks(i);
}
void main
{
int i;
for(i=0;i<5;i++){
state[i] = thingking;
self[i].value = 0;
}
}
在上述程序中, 自定义数据类型status用来枚举哲学家的状态,数组state用来存放五个哲学家的状态,由于该数组是全局变量,所以用信号灯变量mutex实现对它的互斥访问。信号量数组self包含五个元素,每个元素的初始值皆为0,当第i号哲学家不具备进食条件时,会将自己阻塞在信号量self[i]上。函数test用于测试i号哲学家是否具备进食的条件。i号哲学家可以进食必须同时满足以下条件:i号哲学家饥饿,左边哲学家不在进食,右边哲学家不在进食。
【程序代码】
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <curses.h>
#include <time.h>
#include <semaphore.h>
#include <string.h>
#define MAX_PHILOSOPHERS 5
#define ZERO 48
#define DELAY (rand()%25)/1000
typedef enum {thinking,hungry,eating}status; //枚举哲学家的状态
status state[MAX_PHILOSOPHERS]; //存放五个哲学家的状态,全局变量
pthread_mutex_t pre_mutex; //所以用信号灯变量mutex实现对state的互斥访问
sem_t pre_self[MAX_PHILOSOPHERS]; //信号量数组self包含五个元素,每个元素的初始值皆为0
pthread_mutex_t h_mutex_chopsticks[MAX_PHILOSOPHERS] //互斥体数组实现对每一根筷子的互斥使用
int thread_number[MAX_PHILOSOPHERS]={0,1,2,3,4};
void pre_test(int i);
void pre_pick_fork(int i);
void pre_put_fork(int i);
//演示死锁情况的哲学家线程函数
void* deadlock_philosopher(void* data){
int philosopher_number=*(int *)(data);
int i=0;
for(;;)
{
srand( (unsigned)time( NULL ) * ( philosopher_number+ 1) ); //随机等待一段时间
sleep(DELAY);
if(i>=5){
i=0;
clear();
refresh();
}
else
i++;
printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number,
" is waiting chopstick ",ZERO+philosopher_number); //提示等待左筷子
refresh();
pthread_mutex_lock(&h_mutex_chopsticks[philosopher_number]);
sleep(DELAY/4); //随机等待一段时间
printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number,
" is waiting chopstick ",ZERO+(1+philosopher_number)%MAX_PHILOSOPHERS);
refresh();
pthread_mutex_lock(
&h_mutex_chopsticks[(1+philosopher_number)%MAX_PHILOSOPHERS]
); //申请右筷子
printw("%s%c%s\n","Philosopher",ZERO+philosopher_number,"is eating.");
//提示正在进餐
refresh();
sleep(DELAY);
printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number,
" is releasing chopstick ",ZERO+philosopher_number); //放下左筷子
refresh();
pthread_mutex_unlock(&h_mutex_chopsticks[philosopher_number]);
printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number,
"is releasing chopstick ", ZERO+(1+philosopher_number) //放下右筷子
%MAX_PHILOSOPHERS);
refresh();
pthread_mutex_unlock(
&h_mutex_chopsticks[(1+philosopher_number)%MAX_PHILOSOPHERS]
);
sleep(DELAY);
}
return 0;
}
//初始化函数:创建五个哲学家并等待它们结束
void deadlock(){
int i=0;
pthread_t h_thread[MAX_PHILOSOPHERS];
printw("deadlock possible.\n");
refresh();
for(i=0;i<MAX_PHILOSOPHERS;i++){
pthread_mutex_init(&h_mutex_chopsticks[i],NULL);
};
for(i=0;i<MAX_PHILOSOPHERS;i++){
pthread_create(&h_thread[i],NULL,deadlock_philosopher,&thread_number[i]);
};
for(i=0;i<MAX_PHILOSOPHERS;i++){
pthread_join(h_thread[i],NULL);
}
}
//通过按序分配法防止死锁的哲学家线程函数
void* ordered_allocation_philosopher(void* data){
int philosopher_number=*(int *)(data);
int i=0;
for(;;)
{
srand( (unsigned)time( NULL ) * ( philosopher_number+ 1) ); //随机等待一段时间
sleep(DELAY);
if(i>=5){
i=0;
clear();
refresh();
}
else
i++;
if(philosopher_number==MAX_PHILOSOPHERS-1){
printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number,
" is waiting chopstick ",ZERO+(1+philosopher_number)%MAX_PHILOSOPHERS);
refresh(); //提示等待左右两边编号较小的筷子
pthread_mutex_lock(
&h_mutex_chopsticks[(1+philosopher_number)%MAX_PHILOSOPHERS]);
//申请编号较小的筷子
sleep(DELAY/4); //随机等待一段时间
printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number," is waiting chopstick ",ZERO+philosopher_number);
refresh();
pthread_mutex_lock(&h_mutex_chopsticks[philosopher_number]);
} //提示等待左右两边编号较大的筷子
else{
printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number," is waiting chopstick ",ZERO+philosopher_number);
refresh();
pthread_mutex_lock(&h_mutex_chopsticks[philosopher_number]);
sleep(DELAY/4);
printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number," is waiting chopstick ",ZERO+(1+philosopher_number)%MAX_PHILOSOPHERS);
refresh(); //提示正在进餐
pthread_mutex_lock(
&h_mutex_chopsticks[(1+philosopher_number)%MAX_PHILOSOPHERS]);
}
printw("%s%c%s\n","Philosopher ",ZERO+philosopher_number," is eating.");
refresh();
sleep(DELAY);
printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number," is releasing chopstick ",ZERO+philosopher_number); //放下编号较小的筷子
refresh();
pthread_mutex_unlock(&h_mutex_chopsticks[philosopher_number]);
printw("%s%c%s%c\n","Philosopher ",ZERO+philosopher_number," is releasing chopstick ",ZERO+(1+philosopher_number)%MAX_PHILOSOPHERS);
refresh(); //放下编号较大的筷子
pthread_mutex_unlock(&h_mutex_chopsticks[(1+philosopher_number)%MAX_PHILOSOPHERS]
);
sleep(DELAY);
}
return 0;
}
//初始化函数:创建五个哲学家并等待它们结束
void* ordered_allocation(){
int i=0;
pthread_t h_thread[MAX_PHILOSOPHERS]; //线程句柄数组h_thread来表示五个线程,主线程通过等待这五个线程句柄来实现同步
printw("orderded allocation:deadlock impossible.\n");
refresh();
for(i=0;i<MAX_PHILOSOPHERS;i++){
pthread_mutex_init(&h_mutex_chopsticks[i],NULL);
};
for(i=0;i<MAX_PHILOSOPHERS;i++){
pthread_create(&h_thread[i],NULL,ordered_allocation_philosopher,&thread_number[i]);
};
for(i=0;i<MAX_PHILOSOPHERS;i++){
pthread_join(h_thread[i],NULL);
}
}
//通过预先分配法防止死锁的哲学家线程函数
void* pre_allocation_philosopher(void* data){
int philosopher_number=*((int*)(data));
int i=0;
for(;;)
{
srand( (unsigned)time( NULL ) * ( philosopher_number+ 1) );
sleep(DELAY); //随机等待一段时间
if(i>=10){
i=0;
clear();
refresh();
}
else
i++;
printw("%s%c%s\n","Philosopher ",ZERO+philosopher_number,"is thinking ");
refresh(); //提示等待左边筷子
state[philosopher_number]=thinking;
pre_pick_fork(philosopher_number);
printw("%s%c%s\n","Philosopher ",ZERO+philosopher_number," is eating.");
refresh(); //提示等待右筷子
state[philosopher_number]=eating;
sleep(DELAY);
pre_put_fork(philosopher_number);
sleep(DELAY);
}
return 0;
}
void pre_pick_fork(int i){
pthread_mutex_lock(&pre_mutex);
state[i]=hungry;
printw("%s%c%s\n","Philosopher ",ZERO+i," is hungry. ");
pre_test(i);
pthread_mutex_unlock(&pre_mutex);
sem_wait(&pre_self[i]);
}
void pre_put_fork(int i){
pthread_mutex_lock(&pre_mutex);
state[i]=thinking;
pre_test((i-1)%MAX_PHILOSOPHERS); //放下左筷子
pre_test((i+1)%MAX_PHILOSOPHERS); //放下右筷子
pthread_mutex_unlock(&pre_mutex);
}
void pre_test(int i){
if((state[i]==hungry)
&&(state[(i-1)%MAX_PHILOSOPHERS]!=eating)
&&(state[(i+1)%MAX_PHILOSOPHERS]!=eating)){
state[i]=eating; //提示正在就餐
sem_post(&pre_self[i]);
}
}
//初始化函数:创建五个哲学家并等待它们结束
void pre_alloction(){
int i=0;
pthread_t h_thread[MAX_PHILOSOPHERS];
pthread_mutex_init(&pre_mutex,NULL);
printw("pre_allocation:deadlock impossible.\n");
refresh();
for(i=0;i<MAX_PHILOSOPHERS;i++){
sem_init(&pre_self[i],0,0);
state[i]=thinking;
};
for(i=0;i<MAX_PHILOSOPHERS;i++){
pthread_create(&h_thread[i],NULL,pre_allocation_philosopher,&thread_number[i]);
};
for(i=0;i<MAX_PHILOSOPHERS;i++){
pthread_join(h_thread[i],NULL);
}
pthread_mutex_destroy(&pre_mutex);
}
//显示主菜单,接收用户的选择并执行相应的功能。
int main(int argc,char *argv[]){
char select;
bool end=false;
initscr();
while(!end){
clear();
refresh();
printw("|-----------------------------------------|\n");
printw("| 1:deadlock |\n");
printw("| 2:non_deadlock by ordered allocation |\n");
printw("| 3:non_deadlock by pre_allocation |\n");
printw("| 4:exit |\n");
printw("|-----------------------------------------|\n");
printw("select a function(1~4):");
do{
select=(char)getch();
}while(select!='1'&&select!='2'&&select!='3'&&select!='4');
clear();
refresh();
switch(select){
case '1':
deadlock();
break;
case '2':
ordered_allocation();
break;
case '3':
pre_alloction();
break;
case '4':
end=true;
}
printw("\nPress any key to return to main menu.");
getch();
clear();
refresh();
}
endwin();
return 0;
}
【实验结果】
【实验心得】
由荷兰学者Dijkstra提出的哲学家进餐问题(The Dinning Philosophers Problem)是经典的同步问题之一。哲学家进餐问题是一大类并发控制问题的典型例子,涉及信号量机制、管程机制以及死锁等操作系统中关键问题的应用,在操作系统文化史上具有非常重要的地位。对该问题的剖析有助于深刻地理解计算机系统中的资源共享、进程同步机制、死锁等问题,并能熟练地将该问题的解决思想应用于生活中的控制流程。通过本次实验,我受益匪浅。
来源:https://blog.csdn.net/qq_41587612/article/details/98655467