操作系统原理实验三(二)

写在开头

继续完成操作原理实验三
博主选做完成的是第2,3,5题。

注:第2,3题在上一篇戳:操作系统原理实验三(一)

【当堂完成:[1,3,4]中任意 1 题和第2,5 题,共计 3 道题。】
(1)在 Ubuntu 或 Fedora 环境创建一对父子进程,使用共享内存的方式实现进程间的通信。父进程提供数据(1-100,递增),子进程读出来并显示。
(2)(考虑信号通信机制)在 Ubuntu 或 Fedora 环境创建父子 2 个进程 A,B。进程 A 不断获取用户从键盘输入的字符串或整数,通过信号机制传给进程 B。如果输入的是字符串,进程 B 将
其打印出来;如果输入的是整数,进程 B 将其累加起来,并输出该数和累加和。当累加和大于 100 时结束子进程,子进程输出“My work done!”后结束,然后父进程也结束。
(3)在 windows 环境使用创建一对父子进程,使用管道(pipe)的方式实现进程间的通信。父进程提供数据(1-100,递增),子进程读出来并显示。
(4)(考虑匿名管道通信)在 windows 环境下创建将 CMD 控制台程序封装为标准的 windows 窗口程序。
(5)在 windows 环境下,利用高级语言编程环境(限定为 VS 环境或 VC 环境或QT)调用 CreateThread 函数哲学家就餐问题的演示。要求:(1)提供死锁的解法和非死锁的解法;(2)有图形界面直观显示哲学家取筷子,吃饭,放筷子,思考等状态。(3)为增强结果的随机性,各个状态之间的维持时间采用随机时间,例如100ms-500ms 之间。

3.5 Windows下哲学家问题

简述一下哲学家问题:
5名哲学家围桌而作,他们的循环进行着 思考-休息-吃饭 三个环节,
餐桌上只有5只筷子,必须同时拥有两只筷子才可以吃饭。
设哲学家先拿起左边筷子,再拿起右边筷子,然后吃饭。
于是就有可能发生,5名哲学家同时拿起左筷子,却又拿不到右筷子的情况,构成死锁条件
另:给哲学家和筷子进行编号(哲学家与左手边筷子编号相同)

运行效果

操作系统原理实验三(二)_第1张图片
操作系统原理实验三(二)_第2张图片
操作系统原理实验三(二)_第3张图片

有时还会发生死锁:
操作系统原理实验三(二)_第4张图片

代码展示

代码环境:VS2019+Easyx图形库(easyx图形库自行百度安装)
这里给出的是完整代码。完整代码有三部分:死锁情况、解法一、解法二
解法一、解法二仅仅是在死锁情况上做了稍许修改。
(代码开头有注释解释本解法的原理,仅修改了哲学家线程,可以只看这一部分)
死锁情况:

#include
#include
#include 
#include 
#include 

#define P(S) WaitForSingleObject(S,INFINITE); //定义Windows下的P操作
#define V(S)  ReleaseSemaphore(S,1,NULL)	  //定义Windows下的V操作

//预设5个哲学家  5只筷子
#define num_of_philosopher 5
#define num_of_chopsticks 5

//定义互斥量  分别代表5只筷子
HANDLE S[5];

//记录每个哲学家的状态  0->思考 1->休息 2->取得了左边筷子 3->两只筷子都获得,在吃饭
//记录了状态是为了做图形界面时可以直接读取到每个哲学家的状态
int status[5] = {
      0 };


//定义线程 哲学家
//参数  pM-->哲学家编号  
DWORD WINAPI philosopher(LPVOID pM)
{
     
	
	int id = *((int*)(pM));//获得哲学家编号
	//printf("%d号哲学家\n",id);
	//Sleep(10);
	while (1)
	{
     
		//他们思考、休息、吃饭的时间由随机数决定(100~500ms)
		int time_of_thinking = rand() % 401 + 100;
		int time_of_resting = rand() % 401 + 100;
		int time_of_eating = rand() % 401 + 100;

		//哲学家处于思考状态
		status[id] = 0;
	//	printf("%d号哲学家正在思考\n", id);
		Sleep(time_of_thinking);

		//哲学家处于休息状态
		status[id] = 1;
	//	printf("%d号哲学家正在休息\n", id);
		Sleep(time_of_resting);

		//哲学家处于吃饭状态
		P(S[id]);				//取左边筷子
		status[id] = 2;
		//printf("%d号哲学家取得了左边筷子\n", id);
		P(S[(id + 4) % 5]);		//取右边筷子
		status[id] = 3;
	//	printf("%d号哲学家取得了右边筷子,正在吃饭\n", id);
		
		Sleep(time_of_eating);

		V(S[(id + 4) % 5]);		//放下右边筷子
		V(S[id]);				//放下左边筷子

		//一次流程到这里就结束了
	}
}

//绘图相关
IMAGE  desk_img, chopstick_0, chopstick_1, chopstick_2, chopstick_3, chopstick_4;
IMAGE  thinking_imgs[5],resting_imgs[5],left_imgs[5],eating_imgs[5];
void load_image()
{
     
	loadimage(&desk_img, _T("./圆桌.jpg"), 600, 600);

	loadimage(&chopstick_0, _T("./筷子_0.png"),60, 60);
	loadimage(&chopstick_1, _T("./筷子_1.png"), 60, 60);
	loadimage(&chopstick_2, _T("./筷子_2.png"), 60, 60);
	loadimage(&chopstick_3, _T("./筷子_3.png"), 60, 60);
	loadimage(&chopstick_4, _T("./筷子_4.png"), 60, 60);
	
	loadimage(&thinking_imgs[0], _T("./思考_0.png"), 137, 64);
	loadimage(&thinking_imgs[1], _T("./思考_1.png"), 137, 64);
	loadimage(&thinking_imgs[2], _T("./思考_2.png"), 137, 64);
	loadimage(&thinking_imgs[3], _T("./思考_3.png"), 137, 64);
	loadimage(&thinking_imgs[4], _T("./思考_4.png"), 137, 64);

	loadimage(&resting_imgs[0], _T("./休息_0.png"), 137, 64);
	loadimage(&resting_imgs[1], _T("./休息_1.png"), 137, 64);
	loadimage(&resting_imgs[2], _T("./休息_2.png"), 137, 64);
	loadimage(&resting_imgs[3], _T("./休息_3.png"), 137, 64);
	loadimage(&resting_imgs[4], _T("./休息_4.png"), 137, 64);

	loadimage(&left_imgs[0], _T("./左筷_0.png"), 137, 64);
	loadimage(&left_imgs[1], _T("./左筷_1.png"), 137, 64);
	loadimage(&left_imgs[2], _T("./左筷_2.png"), 137, 64);
	loadimage(&left_imgs[3], _T("./左筷_3.png"), 137, 64);
	loadimage(&left_imgs[4], _T("./左筷_4.png"), 137, 64);

	loadimage(&eating_imgs[0], _T("./吃饭_0.png"), 137, 64);
	loadimage(&eating_imgs[1], _T("./吃饭_1.png"), 137, 64);
	loadimage(&eating_imgs[2], _T("./吃饭_2.png"), 137, 64);
	loadimage(&eating_imgs[3], _T("./吃饭_3.png"), 137, 64);
	loadimage(&eating_imgs[4], _T("./吃饭_4.png"), 137, 64);

}
// 载入PNG图并去透明部分
void drawAlpha(int  picture_x, int picture_y, IMAGE* picture) //x为载入图片的X坐标,y为Y坐标
{
     

	// 变量初始化
	DWORD* dst = GetImageBuffer();    // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
	DWORD* draw = GetImageBuffer();
	DWORD* src = GetImageBuffer(picture); //获取picture的显存指针
	int picture_width = picture->getwidth(); //获取picture的宽度,EASYX自带
	int picture_height = picture->getheight(); //获取picture的高度,EASYX自带
	int graphWidth = getwidth();       //获取绘图区的宽度,EASYX自带
	int graphHeight = getheight();     //获取绘图区的高度,EASYX自带
	int dstX = 0;    //在显存里像素的角标

	// 实现透明贴图 公式: Cp=αp*FP+(1-αp)*BP , 贝叶斯定理来进行点颜色的概率计算
	for (int iy = 0; iy < picture_height; iy++)
	{
     
		for (int ix = 0; ix < picture_width; ix++)
		{
     
			int srcX = ix + iy * picture_width; //在显存里像素的角标
			int sa = ((src[srcX] & 0xff000000) >> 24); //0xAArrggbb;AA是透明度
			int sr = ((src[srcX] & 0xff0000) >> 16); //获取RGB里的R
			int sg = ((src[srcX] & 0xff00) >> 8);   //G
			int sb = src[srcX] & 0xff;              //B
			if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
			{
     
				dstX = (ix + picture_x) + (iy + picture_y) * graphWidth; //在显存里像素的角标
				int dr = ((dst[dstX] & 0xff0000) >> 16);
				int dg = ((dst[dstX] & 0xff00) >> 8);
				int db = dst[dstX] & 0xff;
				draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16)  //公式: Cp=αp*FP+(1-αp)*BP  ; αp=sa/255 , FP=sr , BP=dr
					| ((sg * sa / 255 + dg * (255 - sa) / 255) << 8)         //αp=sa/255 , FP=sg , BP=dg
					| (sb * sa / 255 + db * (255 - sa) / 255);              //αp=sa/255 , FP=sb , BP=db
			}
		}
	}
}
void draw()
{
     
	cleardevice();
	BeginBatchDraw();

	setbkcolor(RGB(255, 255, 255));
	cleardevice();//用白色清空背景板

	//画桌子
	putimage(0, 50 , &desk_img);

	//画筷子
	if (status[0] <= 1 && status[1] <= 1)
		drawAlpha(325, 252, &chopstick_0);

	if (status[1] <= 1 && status[2] <= 1)
		drawAlpha(362, 397, &chopstick_1);

	if (status[2] <= 1 && status[3] <= 1)
		drawAlpha(270, 447, &chopstick_2);

	if (status[3] <= 1 && status[4] <= 1)
		drawAlpha(178, 397, &chopstick_3);

	if (status[4] <= 1 && status[0] <= 1)
		drawAlpha(208, 258, &chopstick_4);

	//画状态
	for (int id = 0; id < num_of_philosopher; id++)
	{
     
		int X, Y;
		switch (id) 
		{
     
			case 0:
				X = 232;
				Y = 0;
				break;
			case 1:
				X = 450;
				Y = 150;
				break;
			case 2:
				X = 370;
				Y = 636;
				break;
			case 3:
				X = 100;
				Y = 636;
				break;
			case 4:
				X = 0;
				Y = 150;
				break;
		}
		
		if(status[id]==0)
			drawAlpha(X, Y, &thinking_imgs[id]);
		else if(status[id] == 1)
			drawAlpha(X, Y, &resting_imgs[id]);
		else if (status[id] == 2)
			drawAlpha(X, Y, &left_imgs[id]);
		else if (status[id] == 3)
			drawAlpha(X, Y, &eating_imgs[id]);
	}

	EndBatchDraw();
}
int main()
{
     
	srand((unsigned)time(NULL));

	//创建互斥量 
	for(int i=0;i<num_of_chopsticks;i++)
		S[i] = CreateSemaphore(NULL, 1, 1, NULL);

	//创建线程
	HANDLE hThread[num_of_philosopher];
	int pID[5];
	for (int i = 0; i < num_of_philosopher; i++)
	{
     
		pID[i] = i ;//为什么要设置一个数组,因为传参传的是指针
		//参数:  (LPVOID*)&pID[i]是哲学家编号
		if ((hThread[i] = CreateThread(NULL, 0, philosopher, (LPVOID*)&pID[i], 0, NULL)) == NULL)
		{
     
			printf("Create %d philosopher_thread failed...",i);
			exit(1);
		}
	}

	//画图
	initgraph(600, 700);
	load_image();
	while (1)
	{
     
		//如果不在画图前暂停线程的话,那么在画图过程中线程的状态就可能改变,而使得在图上表现出冲突
		SuspendThread(hThread);//暂停线程
		draw();
		ResumeThread(hThread);//继续线程
	}
	
	//等待线程退出
	WaitForMultipleObjects(num_of_philosopher, hThread, TRUE, INFINITE);
}

解法一:

/*
	解法一:限制最多有4个人同时去拿左边的筷子,
			那么一定有一个哲学家可以完成吃饭,他放下筷子后其他哲学家可以继续进行吃饭
			因此不会形成死锁
	实现方法:设置信号量 full  初值为4 哲学家拿左筷之前执行P(full)
*/
#include
#include
#include 
#include 
#include 

#define P(S) WaitForSingleObject(S,INFINITE); //定义Windows下的P操作
#define V(S)  ReleaseSemaphore(S,1,NULL)	  //定义Windows下的V操作

//预设5个哲学家  5只筷子  最多4个同时拿起左筷
#define num_of_philosopher 5
#define num_of_chopsticks 5
#define max_num 4

//定义互斥量  分别代表5只筷子
HANDLE S[5];
//定义信号量  初值为4 代表最大拿起左筷人数
HANDLE full;

//记录每个哲学家的状态  0->思考 1->休息 2->取得了左边筷子 3->两只筷子都获得,在吃饭
//记录了状态是为了做图形界面时可以直接读取到每个哲学家的状态
int status[5] = {
      0 };


//定义线程 哲学家
//参数  pM-->哲学家编号  
DWORD WINAPI philosopher(LPVOID pM)
{
     

	int id = *((int*)(pM));//获得哲学家编号
	//printf("%d号哲学家\n",id);
	//Sleep(10);
	while (1)
	{
     
		//他们思考、休息、吃饭的时间由随机数决定(100~500ms)
		int time_of_thinking = rand() % 401 + 100;
		int time_of_resting = rand() % 401 + 100;
		int time_of_eating = rand() % 401 + 100;

		//哲学家处于思考状态
		status[id] = 0;
		//	printf("%d号哲学家正在思考\n", id);
		Sleep(time_of_thinking);

		//哲学家处于休息状态
		status[id] = 1;
		//	printf("%d号哲学家正在休息\n", id);
		Sleep(time_of_resting);

		//哲学家处于吃饭状态
		P(full);				//最多4个人同时拿起左筷
		P(S[id]);				//取左边筷子
		status[id] = 2;
		//printf("%d号哲学家取得了左边筷子\n", id);
		P(S[(id + 4) % 5]);		//取右边筷子
		status[id] = 3;
		//	printf("%d号哲学家取得了右边筷子,正在吃饭\n", id);

		Sleep(time_of_eating);

		V(S[(id + 4) % 5]);		//放下右边筷子
		V(S[id]);				//放下左边筷子
		V(full);
		//一次流程到这里就结束了
	}
}
IMAGE  desk_img, chopstick_0, chopstick_1, chopstick_2, chopstick_3, chopstick_4;
IMAGE  thinking_imgs[5], resting_imgs[5], left_imgs[5], eating_imgs[5];
void load_image()
{
     
	loadimage(&desk_img, _T("./圆桌.jpg"), 600, 600);

	loadimage(&chopstick_0, _T("./筷子_0.png"), 60, 60);
	loadimage(&chopstick_1, _T("./筷子_1.png"), 60, 60);
	loadimage(&chopstick_2, _T("./筷子_2.png"), 60, 60);
	loadimage(&chopstick_3, _T("./筷子_3.png"), 60, 60);
	loadimage(&chopstick_4, _T("./筷子_4.png"), 60, 60);

	loadimage(&thinking_imgs[0], _T("./思考_0.png"), 137, 64);
	loadimage(&thinking_imgs[1], _T("./思考_1.png"), 137, 64);
	loadimage(&thinking_imgs[2], _T("./思考_2.png"), 137, 64);
	loadimage(&thinking_imgs[3], _T("./思考_3.png"), 137, 64);
	loadimage(&thinking_imgs[4], _T("./思考_4.png"), 137, 64);

	loadimage(&resting_imgs[0], _T("./休息_0.png"), 137, 64);
	loadimage(&resting_imgs[1], _T("./休息_1.png"), 137, 64);
	loadimage(&resting_imgs[2], _T("./休息_2.png"), 137, 64);
	loadimage(&resting_imgs[3], _T("./休息_3.png"), 137, 64);
	loadimage(&resting_imgs[4], _T("./休息_4.png"), 137, 64);

	loadimage(&left_imgs[0], _T("./左筷_0.png"), 137, 64);
	loadimage(&left_imgs[1], _T("./左筷_1.png"), 137, 64);
	loadimage(&left_imgs[2], _T("./左筷_2.png"), 137, 64);
	loadimage(&left_imgs[3], _T("./左筷_3.png"), 137, 64);
	loadimage(&left_imgs[4], _T("./左筷_4.png"), 137, 64);

	loadimage(&eating_imgs[0], _T("./吃饭_0.png"), 137, 64);
	loadimage(&eating_imgs[1], _T("./吃饭_1.png"), 137, 64);
	loadimage(&eating_imgs[2], _T("./吃饭_2.png"), 137, 64);
	loadimage(&eating_imgs[3], _T("./吃饭_3.png"), 137, 64);
	loadimage(&eating_imgs[4], _T("./吃饭_4.png"), 137, 64);

}
// 载入PNG图并去透明部分
void drawAlpha(int  picture_x, int picture_y, IMAGE* picture) //x为载入图片的X坐标,y为Y坐标
{
     

	// 变量初始化
	DWORD* dst = GetImageBuffer();    // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
	DWORD* draw = GetImageBuffer();
	DWORD* src = GetImageBuffer(picture); //获取picture的显存指针
	int picture_width = picture->getwidth(); //获取picture的宽度,EASYX自带
	int picture_height = picture->getheight(); //获取picture的高度,EASYX自带
	int graphWidth = getwidth();       //获取绘图区的宽度,EASYX自带
	int graphHeight = getheight();     //获取绘图区的高度,EASYX自带
	int dstX = 0;    //在显存里像素的角标

	// 实现透明贴图 公式: Cp=αp*FP+(1-αp)*BP , 贝叶斯定理来进行点颜色的概率计算
	for (int iy = 0; iy < picture_height; iy++)
	{
     
		for (int ix = 0; ix < picture_width; ix++)
		{
     
			int srcX = ix + iy * picture_width; //在显存里像素的角标
			int sa = ((src[srcX] & 0xff000000) >> 24); //0xAArrggbb;AA是透明度
			int sr = ((src[srcX] & 0xff0000) >> 16); //获取RGB里的R
			int sg = ((src[srcX] & 0xff00) >> 8);   //G
			int sb = src[srcX] & 0xff;              //B
			if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
			{
     
				dstX = (ix + picture_x) + (iy + picture_y) * graphWidth; //在显存里像素的角标
				int dr = ((dst[dstX] & 0xff0000) >> 16);
				int dg = ((dst[dstX] & 0xff00) >> 8);
				int db = dst[dstX] & 0xff;
				draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16)  //公式: Cp=αp*FP+(1-αp)*BP  ; αp=sa/255 , FP=sr , BP=dr
					| ((sg * sa / 255 + dg * (255 - sa) / 255) << 8)         //αp=sa/255 , FP=sg , BP=dg
					| (sb * sa / 255 + db * (255 - sa) / 255);              //αp=sa/255 , FP=sb , BP=db
			}
		}
	}
}
void draw()
{
     
	cleardevice();
	BeginBatchDraw();

	setbkcolor(RGB(255, 255, 255));
	cleardevice();//用白色清空背景板

	//画桌子
	putimage(0, 50, &desk_img);

	//画筷子
	if (status[0] <= 1 && status[1] <= 1)
		drawAlpha(325, 252, &chopstick_0);

	if (status[1] <= 1 && status[2] <= 1)
		drawAlpha(362, 397, &chopstick_1);

	if (status[2] <= 1 && status[3] <= 1)
		drawAlpha(270, 447, &chopstick_2);

	if (status[3] <= 1 && status[4] <= 1)
		drawAlpha(178, 397, &chopstick_3);

	if (status[4] <= 1 && status[0] <= 1)
		drawAlpha(208, 258, &chopstick_4);

	//画状态
	for (int id = 0; id < num_of_philosopher; id++)
	{
     
		int X, Y;
		switch (id)
		{
     
		case 0:
			X = 232;
			Y = 0;
			break;
		case 1:
			X = 450;
			Y = 150;
			break;
		case 2:
			X = 370;
			Y = 636;
			break;
		case 3:
			X = 100;
			Y = 636;
			break;
		case 4:
			X = 0;
			Y = 150;
			break;
		}

		if (status[id] == 0)
			drawAlpha(X, Y, &thinking_imgs[id]);
		else if (status[id] == 1)
			drawAlpha(X, Y, &resting_imgs[id]);
		else if (status[id] == 2)
			drawAlpha(X, Y, &left_imgs[id]);
		else if (status[id] == 3)
			drawAlpha(X, Y, &eating_imgs[id]);
	}

	EndBatchDraw();
}
int main()
{
     
	srand((unsigned)time(NULL));

	//创建互斥量 
	for (int i = 0; i < num_of_chopsticks; i++)
		S[i] = CreateSemaphore(NULL, 1, 1, NULL);
	//创建信号量  初值为max_num
	//参数分别表示  继承/初始化信号值/最大信号值/名称
	full= CreateSemaphore(NULL, max_num, max_num, NULL);



	//创建线程
	HANDLE hThread[num_of_philosopher];
	int pID[5];
	for (int i = 0; i < num_of_philosopher; i++)
	{
     
		pID[i] = i;//为什么要设置一个数组,因为传参传的是指针
		//参数:  (LPVOID*)&pID[i]是哲学家编号
		if ((hThread[i] = CreateThread(NULL, 0, philosopher, (LPVOID*)&pID[i], 0, NULL)) == NULL)
		{
     
			printf("Create %d philosopher_thread failed...", i);
			exit(1);
		}
	}

	//画图
	initgraph(600, 700);
	load_image();
	while (1)
	{
     
		//如果不在画图前暂停线程的话,那么在画图过程中线程的状态就可能改变,而使得在图上表现出冲突
		SuspendThread(hThread);//暂停线程
		draw();
		ResumeThread(hThread);//继续线程
	}

	//等待线程退出
	WaitForMultipleObjects(num_of_philosopher, hThread, TRUE, INFINITE);
}

解法二:

/*
	解法二:当哲学家左右两边的筷子都可用时才允许他去拿筷子,
			因此不会形成死锁
	实现方法:拿起左筷前  检测相邻两位哲学家的状态(<=1,才可以拿)
	缺点:无法设立一个信号量来表示左右两侧哲学家的状态,因此实现时只能在一个while(1)循环里不断地检测if(<=1)条件
			这可能会造成CPU资源的浪费
*/
#include
#include
#include 
#include 
#include 

#define P(S) WaitForSingleObject(S,INFINITE); //定义Windows下的P操作
#define V(S)  ReleaseSemaphore(S,1,NULL)	  //定义Windows下的V操作

//预设5个哲学家  5只筷子
#define num_of_philosopher 5
#define num_of_chopsticks 5

//定义互斥量  分别代表5只筷子
HANDLE S[5];

//记录每个哲学家的状态  0->思考 1->休息 2->取得了左边筷子 3->两只筷子都获得,在吃饭
//记录了状态是为了做图形界面时可以直接读取到每个哲学家的状态
int status[5] = {
      0 };


//定义线程 哲学家
//参数  pM-->哲学家编号  
DWORD WINAPI philosopher(LPVOID pM)
{
     
	
	int id = *((int*)(pM));//获得哲学家编号
	//printf("%d号哲学家\n",id);
	//Sleep(10);
	while (1)
	{
     
		//他们思考、休息、吃饭的时间由随机数决定(100~500ms)
		int time_of_thinking = rand() % 401 + 100;
		int time_of_resting = rand() % 401 + 100;
		int time_of_eating = rand() % 401 + 100;

		//哲学家处于思考状态
		status[id] = 0;
	//	printf("%d号哲学家正在思考\n", id);
		Sleep(time_of_thinking);

		//哲学家处于休息状态
		status[id] = 1;
	//	printf("%d号哲学家正在休息\n", id);
		Sleep(time_of_resting);

		//哲学家处于吃饭状态
		while (1)
		{
     
			//若左右两边的哲学家都没有拿起筷子,这个哲学家才可以去拿筷子
			if (status[(id + 1) % 5] <= 1 && status[(id + 4) % 5] <= 1)
				break;
		}
		P(S[id]);				//取左边筷子
		status[id] = 2;
		//printf("%d号哲学家取得了左边筷子\n", id);
		P(S[(id + 4) % 5]);		//取右边筷子
		status[id] = 3;
	//	printf("%d号哲学家取得了右边筷子,正在吃饭\n", id);
		
		Sleep(time_of_eating);

		V(S[(id + 4) % 5]);		//放下右边筷子
		V(S[id]);				//放下左边筷子

		//一次流程到这里就结束了
	}
}
IMAGE  desk_img, chopstick_0, chopstick_1, chopstick_2, chopstick_3, chopstick_4;
IMAGE  thinking_imgs[5],resting_imgs[5],left_imgs[5],eating_imgs[5];
void load_image()
{
     
	loadimage(&desk_img, _T("./圆桌.jpg"), 600, 600);

	loadimage(&chopstick_0, _T("./筷子_0.png"),60, 60);
	loadimage(&chopstick_1, _T("./筷子_1.png"), 60, 60);
	loadimage(&chopstick_2, _T("./筷子_2.png"), 60, 60);
	loadimage(&chopstick_3, _T("./筷子_3.png"), 60, 60);
	loadimage(&chopstick_4, _T("./筷子_4.png"), 60, 60);
	
	loadimage(&thinking_imgs[0], _T("./思考_0.png"), 137, 64);
	loadimage(&thinking_imgs[1], _T("./思考_1.png"), 137, 64);
	loadimage(&thinking_imgs[2], _T("./思考_2.png"), 137, 64);
	loadimage(&thinking_imgs[3], _T("./思考_3.png"), 137, 64);
	loadimage(&thinking_imgs[4], _T("./思考_4.png"), 137, 64);

	loadimage(&resting_imgs[0], _T("./休息_0.png"), 137, 64);
	loadimage(&resting_imgs[1], _T("./休息_1.png"), 137, 64);
	loadimage(&resting_imgs[2], _T("./休息_2.png"), 137, 64);
	loadimage(&resting_imgs[3], _T("./休息_3.png"), 137, 64);
	loadimage(&resting_imgs[4], _T("./休息_4.png"), 137, 64);

	loadimage(&left_imgs[0], _T("./左筷_0.png"), 137, 64);
	loadimage(&left_imgs[1], _T("./左筷_1.png"), 137, 64);
	loadimage(&left_imgs[2], _T("./左筷_2.png"), 137, 64);
	loadimage(&left_imgs[3], _T("./左筷_3.png"), 137, 64);
	loadimage(&left_imgs[4], _T("./左筷_4.png"), 137, 64);

	loadimage(&eating_imgs[0], _T("./吃饭_0.png"), 137, 64);
	loadimage(&eating_imgs[1], _T("./吃饭_1.png"), 137, 64);
	loadimage(&eating_imgs[2], _T("./吃饭_2.png"), 137, 64);
	loadimage(&eating_imgs[3], _T("./吃饭_3.png"), 137, 64);
	loadimage(&eating_imgs[4], _T("./吃饭_4.png"), 137, 64);

}
// 载入PNG图并去透明部分
void drawAlpha(int  picture_x, int picture_y, IMAGE* picture) //x为载入图片的X坐标,y为Y坐标
{
     

	// 变量初始化
	DWORD* dst = GetImageBuffer();    // GetImageBuffer()函数,用于获取绘图设备的显存指针,EASYX自带
	DWORD* draw = GetImageBuffer();
	DWORD* src = GetImageBuffer(picture); //获取picture的显存指针
	int picture_width = picture->getwidth(); //获取picture的宽度,EASYX自带
	int picture_height = picture->getheight(); //获取picture的高度,EASYX自带
	int graphWidth = getwidth();       //获取绘图区的宽度,EASYX自带
	int graphHeight = getheight();     //获取绘图区的高度,EASYX自带
	int dstX = 0;    //在显存里像素的角标

	// 实现透明贴图 公式: Cp=αp*FP+(1-αp)*BP , 贝叶斯定理来进行点颜色的概率计算
	for (int iy = 0; iy < picture_height; iy++)
	{
     
		for (int ix = 0; ix < picture_width; ix++)
		{
     
			int srcX = ix + iy * picture_width; //在显存里像素的角标
			int sa = ((src[srcX] & 0xff000000) >> 24); //0xAArrggbb;AA是透明度
			int sr = ((src[srcX] & 0xff0000) >> 16); //获取RGB里的R
			int sg = ((src[srcX] & 0xff00) >> 8);   //G
			int sb = src[srcX] & 0xff;              //B
			if (ix >= 0 && ix <= graphWidth && iy >= 0 && iy <= graphHeight && dstX <= graphWidth * graphHeight)
			{
     
				dstX = (ix + picture_x) + (iy + picture_y) * graphWidth; //在显存里像素的角标
				int dr = ((dst[dstX] & 0xff0000) >> 16);
				int dg = ((dst[dstX] & 0xff00) >> 8);
				int db = dst[dstX] & 0xff;
				draw[dstX] = ((sr * sa / 255 + dr * (255 - sa) / 255) << 16)  //公式: Cp=αp*FP+(1-αp)*BP  ; αp=sa/255 , FP=sr , BP=dr
					| ((sg * sa / 255 + dg * (255 - sa) / 255) << 8)         //αp=sa/255 , FP=sg , BP=dg
					| (sb * sa / 255 + db * (255 - sa) / 255);              //αp=sa/255 , FP=sb , BP=db
			}
		}
	}
}
void draw()
{
     
	cleardevice();
	BeginBatchDraw();

	setbkcolor(RGB(255, 255, 255));
	cleardevice();//用白色清空背景板

	//画桌子
	putimage(0, 50 , &desk_img);

	//画筷子
	if (status[0] <= 1 && status[1] <= 1)
		drawAlpha(325, 252, &chopstick_0);

	if (status[1] <= 1 && status[2] <= 1)
		drawAlpha(362, 397, &chopstick_1);

	if (status[2] <= 1 && status[3] <= 1)
		drawAlpha(270, 447, &chopstick_2);

	if (status[3] <= 1 && status[4] <= 1)
		drawAlpha(178, 397, &chopstick_3);

	if (status[4] <= 1 && status[0] <= 1)
		drawAlpha(208, 258, &chopstick_4);

	//画状态
	for (int id = 0; id < num_of_philosopher; id++)
	{
     
		int X, Y;
		switch (id) 
		{
     
			case 0:
				X = 232;
				Y = 0;
				break;
			case 1:
				X = 450;
				Y = 150;
				break;
			case 2:
				X = 370;
				Y = 636;
				break;
			case 3:
				X = 100;
				Y = 636;
				break;
			case 4:
				X = 0;
				Y = 150;
				break;
		}
		
		if(status[id]==0)
			drawAlpha(X, Y, &thinking_imgs[id]);
		else if(status[id] == 1)
			drawAlpha(X, Y, &resting_imgs[id]);
		else if (status[id] == 2)
			drawAlpha(X, Y, &left_imgs[id]);
		else if (status[id] == 3)
			drawAlpha(X, Y, &eating_imgs[id]);
	}

	EndBatchDraw();
}
int main()
{
     
	srand((unsigned)time(NULL));

	//创建互斥量 
	for(int i=0;i<num_of_chopsticks;i++)
		S[i] = CreateSemaphore(NULL, 1, 1, NULL);

	//创建线程
	HANDLE hThread[num_of_philosopher];
	int pID[5];
	for (int i = 0; i < num_of_philosopher; i++)
	{
     
		pID[i] = i ;//为什么要设置一个数组,因为传参传的是指针
		//参数:  (LPVOID*)&pID[i]是哲学家编号
		if ((hThread[i] = CreateThread(NULL, 0, philosopher, (LPVOID*)&pID[i], 0, NULL)) == NULL)
		{
     
			printf("Create %d philosopher_thread failed...",i);
			exit(1);
		}
	}

	//画图
	initgraph(600, 700);
	load_image();
	while (1)
	{
     
		//如果不在画图前暂停线程的话,那么在画图过程中线程的状态就可能改变,而使得在图上表现出冲突
		SuspendThread(hThread);//暂停线程
		draw();
		ResumeThread(hThread);//继续线程
	}
	
	//等待线程退出
	WaitForMultipleObjects(num_of_philosopher, hThread, TRUE, INFINITE);
}

简单解释一下

1.创建线程:

  1. 函数原型:

    HANDLE WINAPI CreateThread(
    In_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
    In SIZE_T dwStackSize,
    In LPTHREAD_START_ROUTINE lpStartAddress,
    In_opt LPVOID lpParameter,
    In DWORD dwCreationFlags,
    Out_opt LPDWORD lpThreadId
    );

  1. 参数说明:
    第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
    第二个参数 dwStackSize 表示线程栈空间大小。传入0表示使用默认大小(1MB)。
    第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
    第四个参数 lpParameter 是传给线程函数的参数。
    第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。
    第六个参数 lpThreadId 将返回线程的ID号,传入NULL表示不需要返回该线程ID号。

3.返回值 线程创建成功返回新线程的句柄,失败返回NULL

我只是理论的搬运工hhhh
附参考链接:windows多线程(一) 创建线程 CreateThread

2.哲学家线程

DWORD WINAPI philosopher(LPVOID pM)
{
     
	int id = *((int*)(pM));//获得哲学家编号
	while(1)
	{
     
	

首先是获取哲学家编号id(0~4)

		//他们思考、休息、吃饭的时间由随机数决定(100~500ms)
		int time_of_thinking = rand() % 401 + 100;
		int time_of_resting = rand() % 401 + 100;
		int time_of_eating = rand() % 401 + 100;

然后通过随机数确定他们思考、休息、吃饭消耗的时间(100~500ms)

		//哲学家处于思考状态
		status[id] = 0;
		//	printf("%d号哲学家正在思考\n", id);
		Sleep(time_of_thinking);

		//哲学家处于休息状态
		status[id] = 1;
		//	printf("%d号哲学家正在休息\n", id);
		Sleep(time_of_resting);

哲学家处于思考态时status[id]=0
处于休息态时设置status[id]=1

		//哲学家处于吃饭状态
		P(S[id]);				//取左边筷子
		status[id] = 2;
		//printf("%d号哲学家取得了左边筷子\n", id);
		P(S[(id + 4) % 5]);		//取右边筷子
		status[id] = 3;
		//	printf("%d号哲学家取得了右边筷子,正在吃饭\n", id);
		
		Sleep(time_of_eating);

		V(S[(id + 4) % 5]);		//放下右边筷子
		V(S[id]);				//放下左边筷子

		//一次流程到这里就结束了
	}
}

哲学家吃饭前要先拿起左边筷子此时status[id]=2
再拿起右边筷子并吃饭,status[id]=3
注意拿筷子改变状态前需要P(S[id])(筷子有一个编号,哲学家与左手边筷子编号相同。)
3.画图
使用了Easyx图形库,大家可以自行百度安装一下,挺方便的。

loadimage()是将图片加载进来
putimage()是将图片贴到背景上去

drawAlpha()是自定义的函数,因为putimage()不能处理透明的图片,所以在网上找到一个大佬的代码,可以实现有透明背景的图片粘贴(筷子的背景是透明的)

图片都是我自己P的,素材来源:感谢阿里爸爸的iconfont图形库
我会在最后把代码及图片资源传上去。
4.解法一

解法一:限制最多有4个人同时去拿左边的筷子,
那么一定有一个哲学家可以完成吃饭,他放下筷子后其他哲学家可以继续进行吃饭
因此不会形成死锁
实现方法:设置信号量 full 初值为4 哲学家拿左筷之前执行P(full),吃完饭后执行V(full)
5.解法二
解法二:当哲学家左右两边的筷子都可用时才允许他去拿筷子,因此不会形成死锁
实现方法:拿起左筷前 检测相邻两位哲学家的状态(<=1,才可以拿)
缺点:无法设立一个信号量来表示左右两侧哲学家的状态,因此实现时只能在一个while(1)循环里不断地检测if(<=1)条件
这可能会造成CPU资源的浪费

资源分享

链接:https://download.csdn.net/download/zsh1184528359/12393157

木得积分可以评论区留下邮箱,我看到了就会发的(●’◡’●)
点击下图画框框的《哲学家就餐.exe》就可以直接运行

操作系统原理实验三(二)_第5张图片
另外:代码中解法一、解法二全篇注释中(因为一次只能运行一个main,所以三个.c只能有一个没有注释)
Ctrl+A 全选,然后Ctrl+K+U解除注释
Ctrl+A 全选,然后Ctrl+K+C全篇注释
操作系统原理实验三(二)_第6张图片
18级软院的哥哥姐姐们,如果用到的话,建议图自己P一下,搞的都一样就不好了(>人<;)

写在结尾

希望以上可以帮到你!
如有错误,或不同想法,欢迎指出,互相学习共同进步!

你可能感兴趣的:(操作系统,c语言,多线程)