哲學家進餐問題描述:
哲學家進餐問題是E.W.Dijkstra 在1965年秋,爲埃因霍溫(Eindhoven)技術大學學生提出的一個考題,原題爲五胞胎進餐問題,不久就以牛津大學教授Hoare(霍爾)所取得名字——“哲學家進餐問題”而聞名。經過中國化得哲學家進餐問題可以這樣描述:5個哲學家同坐在一張圓桌旁,每個人的面前放着一碗麪條,碗的兩旁各擺放一根筷子。假設哲學家的生活除了吃飯就是思考(這是一種抽象,即對該問題而言,其他活動無關緊要),而吃飯的時候要左手拿一根筷子,右手拿一根筷子,然後開始進餐。吃完後又將筷子放回原處,繼續思考問題。
一個哲學家的活動進程描述:
- 思考問題
- 餓了停止思考,左手拿一根筷子(如果左側哲學家已持有它,則需要等待);
- 右手拿一根筷子(如果右側哲學家已持有它,則需要等待);
- 進餐
- 放右手筷子
- 放左手筷子
- 重新回到思考問題的狀態分別考慮下面兩種情況:
- 按哲學家的活動進程,當所有的哲學家都同時拿起左手的筷子時,則所有的哲學家都將拿不到右手的筷子,並處於等待狀態,那麼哲學家都將無法進餐,最終餓死。
- 將哲學家的活動進程修改一下,變爲當右手的筷子拿不到時,就放下左手的筷子,這種情況不一定沒有問題。因爲可能在一瞬間,所有的哲學家都同時拿起左手的筷子,則自然拿不到右手的筷子,於是都同時放下左手的筷子,等一會,又同時拿起左手的筷子,如此這樣永遠重複下去,則所有的哲學家都將無法進餐。
以上兩個方面的問題,其實反映的是程序併發執行時進程同步的兩個問題,一個是死鎖,一個是飢餓。
下面有三種方法避免死鎖:
- 最多允許4位哲學家同時拿起左手(或右手)的筷子。
- 僅當哲學家的左、右兩支筷子均可用時,才允許他同時拿起左、右手的兩支筷子;否則一支筷子也不拿。
- 奇數號哲學家先拿右邊的筷子,然後再取左邊的筷子;偶數號哲學家先拿左邊的筷子,然後再拿右邊的筷子。
代碼1:僅當哲學家的左、右兩支筷子均可用時,才允許他同時拿起左、右手的兩支筷子;否則一支筷子也不拿
#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
HANDLE mutex;//互斥變量
HANDLE chopstick[5];
HANDLE philosopher[5];
int num=0;
int random()
{
return rand()%100+60;
}
void eating(int id)
{
int etime=random();
Sleep(etime);
printf("\t\t\t哲學家%d號吃了%d秒飯\n",id,etime);
}
//用windows下的WINAPI函數實現簡單的多線程,PVOID無類型指針;param參數
DWORD WINAPI phthread(LPVOID param)
{
num++;
int id=num;
int limittime=0;//進餐的次數限制
int chopstick1,chopstick2;//實現左右2根筷子變量
while(true)
{
Sleep(200);//未來100毫秒內不會被喚醒;Unix系統使用的是時間片法,而Windows則屬於搶佔式,將進程掛起
if(limittime>=1)
break;
chopstick1=WaitForSingleObject(chopstick[(id+1)%5],0), chopstick2=WaitForSingleObject(chopstick[id],0);//同時拿起筷子
if(chopstick1==WAIT_OBJECT_0&&chopstick2==WAIT_OBJECT_0)
{
WaitForSingleObject(mutex,INFINITE);//一直佔主線程,到ReleaseMutex(mutex)爲止
printf("哲學家%d號拿到兩隻筷子開始吃飯。\n", id);
ReleaseMutex(mutex);//釋放互斥信號體
limittime++;
WaitForSingleObject(mutex,INFINITE);
eating(id);
ReleaseMutex(mutex);//釋放互斥信號體
WaitForSingleObject(mutex,INFINITE);
printf("\t\t\t哲學家%d號吃完飯啦,放下筷子,繼續思考。\n", id);
ReleaseMutex(mutex);
}
ReleaseSemaphore(chopstick[(id+1)%5], 1, NULL),ReleaseSemaphore(chopstick[id], 1, NULL);//同時放下筷子
WaitForSingleObject(mutex,INFINITE);
ReleaseMutex(mutex);
}
return 0 ;
}
int main()
{
srand((unsigned)time(0));
mutex = CreateMutex(NULL, false, NULL);//創建互斥信號量,傳遞false值
for (int i = 0; i < 5; ++i)
{
chopstick[i]=CreateSemaphore(NULL,1,1,NULL);//創建筷子的信號量
}
for (i = 0; i < 5; ++i)
{
philosopher[i] = CreateThread(NULL, 0, phthread,NULL, 0, NULL);//創建哲學家新線程,線程名爲phthread,傳遞值爲空,返回HANDLE
}
Sleep(10000);
for (i = 0; i < 5; ++i)
{
CloseHandle(philosopher[i]);
CloseHandle(chopstick[i]);
}
CloseHandle(mutex);
Sleep(500);
return 0;
}
代碼2:
奇數號哲學家先拿右邊的筷子,然後再取左邊的筷子;偶數號哲學家先拿左邊的筷子,然後再拿右邊的筷子。
#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
HANDLE mutex;//互斥變量
HANDLE chopstick[5];
HANDLE philosopher[5];
int num;
int random()
{
return rand()%100+60;
}
void eating(int id)
{
int etime=random();
Sleep(etime);
printf("\t\t\t哲學家%d號吃了%d秒飯\n",id,etime);
}
//用windows下的WINAPI函數實現簡單的多線程,PVOID無類型指針;param參數
DWORD WINAPI phthread(LPVOID param)
{
num++;
int limittime=0;//進餐的次數限制
int id=num;//哲學家的號數
int chopstick1,chopstick2;//實現左右2根筷子變量
while(true)
{
Sleep(100);//未來100毫秒內不會被喚醒;Unix系統使用的是時間片法,而Windows則屬於搶佔式
if(limittime>=1)
break;
if(id%2!=0)
{
//奇數號的哲學家先拿起右邊的筷子再拿起左邊的筷子;
chopstick1=WaitForSingleObject(chopstick[(id+1)%5],0);//等待筷子發出信號,如果條件滿足,則返回WAIT_OBJECT_0 ,否則返回WAIT_TIMEOUT
if(chopstick1==WAIT_OBJECT_0)
{
chopstick2=WaitForSingleObject(chopstick[id],0);
if(chopstick2==WAIT_OBJECT_0)
{
WaitForSingleObject(mutex,INFINITE);//一直佔主線程,到ReleaseMutex(mutex)爲止
printf("哲學家%d號拿到兩隻筷子開始吃飯。\n", id);
ReleaseMutex(mutex);//釋放互斥信號體
limittime++;
WaitForSingleObject(mutex,INFINITE);
eating(id);
ReleaseMutex(mutex);//釋放互斥信號體
WaitForSingleObject(mutex,INFINITE);
printf("\t\t\t哲學家%d號吃完飯啦,放下筷子,繼續思考。\n", id);
ReleaseMutex(mutex);
ReleaseSemaphore(chopstick[id], 1, NULL);//釋放當前左邊筷子的信號量
}
//如果哲學家搶到一隻筷子,在搶佔另一隻筷子時失敗,則要放棄已經搶佔到的資源。
ReleaseSemaphore(chopstick[(id+1)%5], 1, NULL);//釋放當前右邊筷子的信號量
}
}
else
{
//偶數號哲學家先拿起左邊的筷子,再拿起右邊的筷子
chopstick1=WaitForSingleObject(chopstick[id],0);
if(chopstick1==WAIT_OBJECT_0)
{
chopstick2=WaitForSingleObject(chopstick[(id+1)%5],0);
if(chopstick2==WAIT_OBJECT_0)
{
//一直佔主線程,到ReleaseMutex(mutex)爲止
WaitForSingleObject(mutex,INFINITE);
printf("哲學家%d號拿到兩隻筷子開始吃飯。\n", id);
ReleaseMutex(mutex);//釋放互斥信號體,不然其他哲學家拿不到筷子
limittime++;
WaitForSingleObject(mutex,INFINITE);
eating(id);
ReleaseMutex(mutex);//釋放互斥信號體
WaitForSingleObject(mutex,INFINITE);
printf("\t\t\t哲學家%d號吃完飯啦,放下筷子,繼續思考。\n", id);
ReleaseMutex(mutex);
//左右兩邊都搶到筷子的哲學家,吃完放後釋放資源
ReleaseSemaphore(chopstick[(id+1)%5], 1, NULL);//釋放當前右邊筷子的信號量
}
//如果哲學家搶到一隻筷子,在搶佔另一隻筷子時失敗,則要放棄已經搶佔到的資源。
ReleaseSemaphore(chopstick[id], 1, NULL);//釋放當前左邊筷子的信號量
}
}
WaitForSingleObject(mutex,INFINITE);
ReleaseMutex(mutex);
}
return 0 ;
}
int main()
{
srand((unsigned)time(0));
//3個參數lpMutecAttributes:指向SECURITY_ATTRIBUTES型態的結構指針,NULL使用默認安全
//blnitialOwner BOOL:建立互斥體,互斥體同時只能一個線程擁有
//lpName String:指定互斥體對象的名子,爲NULL不指定
mutex = CreateMutex(NULL, false, NULL);//創建互斥信號量,傳遞false值
for (int i = 0; i < 5; ++i)
{
//4個參數分別爲lpSemaphoreAttributes:爲信號量的屬性,一般設置爲NULL
//lInitialCount:信號量初始值,爲0 默認爲unsignal狀態,爲 1 默認爲signal狀態
//lpMaximumCount:信號量的最大值爲1
//lpName:信號量的名字,可設置爲NULL
chopstick[i]=CreateSemaphore(NULL,1,1,NULL);//創建筷子的信號量
}
for (i = 0; i < 5; ++i)
{
//6個參數分別爲:lpThreadAttributes:指向SECURITY_ATTRIBUTES型態的結構指針,NULL使用默認安全
//dwStackSize:設置初始棧的大小,爲0 默認將使用與調用該函數的線程相同的棧空間大小
//lpStartAdress:指向線程函數的指針,函數名
//lpParameter:向線程函數傳遞參數,不需要傳遞參數是爲NULL
//dwCreationFlags:線程標誌,爲 0 時,表示創建後立即激活
//lpThreadld:保存新線程的id,返回線程id,不想返回id設置爲NULL
philosopher[i] = CreateThread(NULL, 0, phthread,NULL, 0, NULL);//創建哲學家新線程,線程名爲phthread,傳遞值爲空,返回HANDLE
}
Sleep(10000);
for (i = 0; i < 5; ++i)
{
CloseHandle(philosopher[i]);
CloseHandle(chopstick[i]);
}
CloseHandle(mutex);
Sleep(500);
return 0;
}