1. 程式人生 > >Linux 多執行緒同步之哲學家用餐問題

Linux 多執行緒同步之哲學家用餐問題

問題描述:

有五個哲學家公用一張餐桌,分別坐在周圍的五張椅子上,在餐桌上有五個碗和五隻筷子,他們的生活方式是交替地進行思考和用餐。平時,一個哲學家進行思考,飢餓時便試圖拿取其左右最靠近他的筷子,只有在他拿到兩隻筷子時才能進餐,進餐完畢,放下筷子繼續思考。(計算機作業系統 第三版)

書上為程式碼:

Var chopstick: array[0,...,4] of semaphore;
repeat
	wait(chopstick[i]);
	wait(chopstick[(i+1)%5])
			.
			.
	eat:
			.
			.
	signal(chopstick[i])
	signal(opstick[(i+1)%5])
			.
			.
	think:
until false;
這樣當五個哲學家同時飢餓時而各自拿起左邊的筷子是,就會使五個訊號量chopstick均為0;當他們再試圖去拿右邊的筷子時,都將因沒有筷子可拿而無限地等待。從而導致死鎖。

本文解決死鎖的策略是:只有當哲學家的左右兩隻筷子均可以用的時候,才允許其拿起筷子進餐,否則則該哲學家一直請求左邊的筷子保持阻塞狀態(左邊的筷子訊號量為1則直接佔有,訊號量為0則進入阻塞狀態),右邊的筷子若訊號量為0,則非阻塞,進入等待。

詳細程式碼:

/*
 * philosopher.c
 *
 *  Created on: 2012-12-17
 *      Author: xkey
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>

#define PEOPLE_NUM 5
#define THINKING 1
#define HUNGRY 2
#define EATING 3

sem_t chopsticks[PEOPLE_NUM];
pthread_mutex_t mutex;

void *philosopher(void *arg){
	int id = (int) arg;

	int state = THINKING;
	int right = (id + 1) % PEOPLE_NUM;
	int left = (id + PEOPLE_NUM - 1) % PEOPLE_NUM;
	char ptrState[32];

	while(1){
		switch(state){
		case THINKING:
			usleep(300);
			state = HUNGRY;
			strcpy(ptrState,"Thinking before eat");
			break;
		case HUNGRY:
			strcpy(ptrState,"Hungry");
			if(sem_wait(&chopsticks[left]) == 0){//阻塞狀態
				if(sem_trywait(&chopsticks[right]) == 0){//非阻塞
					strcpy(ptrState,"I will Eating");
					state = EATING;
				}else{
					state = THINKING;
					strcpy(ptrState,"I have not chopsticks");
					sem_post(&chopsticks[left]);//釋放請求的得到的left筷子
					printf("Philosopher right chopsticks is busy,right=%d,thread id is %d\n",right,id);
				}
			}else{
				printf("Philosopher left chopsticks is busy,left=%d,thread id is %d\n",left,id);//這句話由於上面被阻塞永遠不會輸出
			}
			break;
		case EATING:
			printf("Philosopher fetch left and right chopsticks: (%d,%d), threadid is %d\n",left,right,id);
			sem_post(&chopsticks[left]);
			sem_post(&chopsticks[right]);
			printf("Philosopher release left and right chopsticks: (%d,%d), threadid is %d\n",left,right,id);
			usleep(500);
			state = THINKING;
			strcpy(ptrState,"Thinking after eat");
			break;
		}
		pthread_mutex_lock(&mutex);
		printf("Philosopher is %s, thread id is %d\n",ptrState,id);
		pthread_mutex_unlock(&mutex);
		usleep(1000);
	}

	pthread_exit((void*)0);
}

int main(){
	pthread_t tid[PEOPLE_NUM];
	int i;
	pthread_mutex_init(&mutex,NULL);
	for(i = 0 ; i < PEOPLE_NUM ; i ++){
		sem_init(&chopsticks[i],0,1);
	}
	for(i = 0 ; i < PEOPLE_NUM ; i ++){
		pthread_create(&tid[i],NULL,philosopher,(void*)i);
	}
	for(i = 0 ; i < PEOPLE_NUM ; i ++){
		pthread_join(tid[i],NULL);
	}
	return 0;
}

 gcc編譯時,加上-lphread選項,例:gcc philosopher.c -lpthread

這裡有個問題,也就是解決死鎖的策略

if(sem_wait(&chopsticks[left]) == 0){//阻塞狀態
if(sem_trywait(&chopsticks[right]) == 0){//非阻塞

可以考慮如果第二個if使用sem_wait會怎麼樣,是不是還能保證解決死鎖,個人認為均用sem_wait就不需要寫if語句了,這樣應該會造成死鎖;

另:如果兩個if均使用sem_trywait會怎麼樣?

個人覺得這兩個問題是本文應該思考的地方,本身哲學家進餐問題的實現不難,關鍵就是解決死鎖的策略。

本文解決哲學家用餐問題使用的是訊號量方法,該方法詳解的Bolg地址見http://blog.csdn.net/wtz1985/article/details/3826291點選開啟連結

另附兩篇博文都是關於解決哲學家進餐問題的:

1、使用互斥量:http://blog.csdn.net/kongzhp/article/details/7487150

2、使用訊號量(本文參照的方法,但是我看他寫的全部是用sem_wait的):http://www.cnblogs.com/margincc/archive/2011/06/30/2094418.html

附:由於本人對Linux多執行緒研究不深,如有錯誤,歡迎指教,謝謝。

下面貼出上面程式碼的執行結果:

Philosopher is Thinking before eat, thread id is 4
Philosopher fetch left and right chopsticks: (0,2), threadid is 1
Philosopher release left and right chopsticks: (0,2), threadid is 1
Philosopher is Thinking after eat, thread id is 1
Philosopher right chopsticks is busy,right=0,thread id is 4
Philosopher is I have not chopsticks, thread id is 4
Philosopher is Thinking before eat, thread id is 1
Philosopher fetch left and right chopsticks: (4,1), threadid is 0
Philosopher release left and right chopsticks: (4,1), threadid is 0
Philosopher is Thinking before eat, thread id is 3
Philosopher is Thinking before eat, thread id is 4
Philosopher is I will Eating, thread id is 1
Philosopher is Thinking after eat, thread id is 0
Philosopher is Thinking before eat, thread id is 2
Philosopher right chopsticks is busy,right=0,thread id is 4
Philosopher is I have not chopsticks, thread id is 4
Philosopher fetch left and right chopsticks: (0,2), threadid is 1
Philosopher release left and right chopsticks: (0,2), threadid is 1
Philosopher is I will Eating, thread id is 3
Philosopher is I will Eating, thread id is 2
Philosopher is Thinking before eat, thread id is 0
Philosopher is Thinking after eat, thread id is 1
Philosopher is Thinking before eat, thread id is 4
Philosopher fetch left and right chopsticks: (2,4), threadid is 3
Philosopher release left and right chopsticks: (2,4), threadid is 3
Philosopher fetch left and right chopsticks: (1,3), threadid is 2
Philosopher release left and right chopsticks: (1,3), threadid is 2

Philosopher is I will Eating, thread id is 0
Philosopher is Thinking after eat, thread id is 3
Philosopher is Thinking before eat, thread id is 1
Philosopher is I will Eating, thread id is 4
Philosopher is Thinking after eat, thread id is 2
Philosopher fetch left and right chopsticks: (4,1), threadid is 0
Philosopher is Thinking before eat, thread id is 3
Philosopher fetch left and right chopsticks: (3,0), threadid is 4
Philosopher release left and right chopsticks: (3,0), threadid is 4
Philosopher release left and right chopsticks: (4,1), threadid is 0

可以看到,一開始執行緒4就被阻塞,原因就是右邊的筷子0被執行緒1佔有

紅色部分,執行緒4一直不能同時得到左右兩邊的筷子

紫色部分,執行緒2左邊的筷子1是空閒的,但是右邊的筷子3之前已經被執行緒4所請求過,但是正是由於執行緒4不能得到其右邊的筷子0,所以放棄了已經佔有的其左邊筷子3,所以執行緒2能夠執行。

藍色體現了多執行緒的同步執行,除此之外,發現執行緒4同時得到了左右兩邊的筷子3和0