-
问题描述
以下问题描述摘自维基百科:http://zh.wikipedia.org/wiki/%E5%93%B2%E5%AD%A6%E5%AE%B6%E5%B0%B1%E9%A4%90%E9%97%AE%E9%A2%98
哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。
哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。即使没有死锁,也有可能发生资源耗尽。例如,假设规定当哲学家等待另一只餐叉超过五分钟后就放下自己手里的那一只餐叉,并且再等五分钟后进行下一次尝试。这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生“活锁”。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉。
在实际的计算机问题中,缺乏餐叉可以类比为缺乏共享资源。一种常用的计算机技术是资源加锁,用来保证在某个时刻,资源只能被一个程序或一段代码访问。当一个程序想要使用的资源已经被另一个程序锁定,它就等待资源解锁。当多个程序涉及到加锁的资源时,在某些情况下就有可能发生死锁。例如,某个程序需要访问两个文件,当两个这样的程序各锁了一个文件,那它们都在等待对方解锁另一个文件,而这永远不会发生。
-
解决方法
每个哲学家可以用如下函数模拟:
1 void philosopher(int id) 2 { 3 while(1) 4 { 5 thinking(id); 6 take_left_fork(id); 7 take_right_fork(id); 8 eating(id); 9 put_right_fork(id); 10 put_left_fork(id); 11 } 12 }
为了达到资源互斥的使用,必须有效的解决每个刀叉的使用,可以用如下方法解决
- 每个哲学家由思考状态转为就餐状态时,首先将状态置位饥饿状态,然后互斥的检查(即保证同一时刻只有一个哲学家在执行价检查操作)左右两边的哲学家是否在就餐状态,若左右两边的哲学家都没有处于就餐状态,则拿起刀叉,否则,线程阻塞,直至左边和右边的刀叉均被放下时,线程被唤醒
- 拿起刀叉后,哲学家状态由饥饿置为就餐
- 就餐结束后,把哲学家状态由就餐改为思考,并以此检查左右两边边哲学家是否可以就餐(当左边哲学家处于饥饿状态,且左边哲学家的两边哲学家都不处于就餐状态),若可以,则唤醒两边的可以就餐的哲学家线程,并置左右边可以就餐的哲学家状态为就餐,完成放下刀叉操作
以下为代码实现,环境为VS2010:
1 #include<stdio.h> 2 #include<windows.h> 3 #include<process.h> 4 5 #define N 5 //哲学家个数 6 #define LEFT(i) ((i) + N - 1) % N //左边哲学家编号 7 #define RIGHT(i) ((i) + 1) % N //右边哲学家编号 8 #define THINKING 0 //思考状态 9 #define HUNGRY 1 //饥饿状态 10 #define EATING 2 //就餐状态 11 12 int state[N]; //哲学家状态数组 13 HANDLE mutex; //状态访问互斥量,保证任一时刻,只有一个哲学家线程在检查左右两边状态 14 HANDLE semaphore[N]; //哲学家线程信号量,标识哲学家是否获取了刀叉,否则线程阻塞 15 HANDLE semPar; //线程同步信号量, 16 int eattime[N] = {2000,1000,4000,2000,3000}; //此数组为哲学家就餐时长,可为0 17 int thinkingtime[N] = {1000,3000,3000,2000,4000}; //此数组为哲学家思考时长,可为0 18 19 void thinking(int i); 20 void eating(int i); 21 void take_forks(int i); 22 void put_forks(int i); 23 void test(int i); 24 void print_state(void); 25 26 unsigned int __stdcall philosopher(void *pM) 27 { 28 int i,cnt = 3; 29 i = *(int*)pM; 30 //线程编号,同步 31 ReleaseSemaphore(semPar, 1, NULL); 32 33 //就餐cnt次,线程结束 34 while(cnt--) 35 { 36 thinking(i); 37 take_forks(i); 38 eating(i); 39 put_forks(i); 40 } 41 printf(" 哲学家[%d]线程结束\n", i); 42 return 0; 43 } 44 45 void take_forks(int i) 46 { 47 //互斥修改及检查哲学家状态 48 WaitForSingleObject(mutex, INFINITE); 49 state[i] = HUNGRY; 50 test(i); 51 ReleaseSemaphore(mutex, 1, NULL); 52 //若获得了两个刀叉,则继续执行,否则阻塞在此处,等待邻居放下刀叉 53 WaitForSingleObject(semaphore[i], INFINITE); 54 } 55 56 void put_forks(int i) 57 { 58 WaitForSingleObject(mutex, INFINITE); 59 state[i] = THINKING; 60 test(LEFT(i)); //检查左边哲学家是否可以就餐 61 test(RIGHT(i)); //检查右边哲学家是否可以就餐 62 ReleaseSemaphore(mutex, 1, NULL); 63 } 64 65 void test(int i) 66 { 67 if(state[i] == HUNGRY && state[LEFT(i)] != EATING && state[RIGHT(i)] != EATING) 68 { 69 state[i] = EATING; 70 ReleaseSemaphore(semaphore[i], 1, NULL); 71 } 72 } 73 74 void eating(int i) 75 { 76 printf("哲学家[%d]开始就餐\n", i); 77 Sleep(eattime[i]); 78 printf("哲学家[%d]结束就餐\n\n", i); 79 } 80 81 void thinking(int i) 82 { 83 Sleep(thinkingtime[i]); 84 } 85 86 void print_state(void) 87 { 88 int i; 89 for(i = 0;i < N;i++) 90 { 91 printf("%d ", state[i]); 92 } 93 printf("\n"); 94 } 95 96 int main() 97 { 98 int i; 99 HANDLE thread[N]; 100 mutex = CreateSemaphore(NULL, 1, 1, NULL); 101 102 for(i = 0;i < N;i++) 103 { 104 semaphore[i] = CreateSemaphore(NULL, 0, 1, NULL); 105 } 106 107 semPar = CreateSemaphore(NULL, 0, 1, NULL); 108 109 for(i = 0;i < N;i++) 110 { 111 thread[i] = (HANDLE)_beginthreadex(NULL, 0,philosopher, &i, 0, NULL); 112 WaitForSingleObject(semPar, INFINITE); 113 } 114 WaitForMultipleObjects(N, thread, TRUE, INFINITE); 115 system("pause"); 116 return 0; 117 }