操作系统:小和尚打水+老和尚喝水经典同步问题实现 菜鸟的解题全过程(附具体代码)

操作系统刚上两周网课老师便布置了两道现阶段本人觉得是课设的”课设“,第一道题在之前的博客中报告过了,下面是第2道题的菜鸟报告。上一篇博客中有初次做这道题的具体分析过程了,这里就侧重后面的代码实现部分的分析。

先上题目:

题目

某寺庙,有小和尚、老和尚若干。有一水缸,由小和尚提水入缸,老和尚从缸中取水饮用。水缸可容纳10桶水,水取自同一水井中,水井径窄,每次只能容一个水桶取水。水桶总数为3个,每次入、取缸水仅为1桶,且不可同时进行。试给出取水、入水的算法描述。

1、初解

1、乍一看以为只是算法描述,然后课本和一些网上资源也只是P、V操作的伪代码。因此我的分析是这样子的:
看题过程把重点信息找出来(即找出资源),然后开始进一步捋清流程。
小和尚打水入缸流程:
拿桶--------->去水井取水(互斥,P、V操作)--------->把水倒入水缸(互斥,P、V操作)--------->放桶
老和尚取水饮用流程:
拿桶--------->去水缸取水(互斥,P、V操作)--------->放桶
2、伪代码如下:

semaphore  mutex_well = 1, mutex_vat = 1;//互斥量
semaphore  pail = 3, empty = 10, full = 0;//定义自然量,empty表示水的总容量,full表示满的标志
project  small()//小和尚
{
 while (true)
 {
  P(empty); //判断水缸是否还有容量,有则减一,程序向下执行
  P(pail);  //申请一个桶
  P(mutex_well);  //占用水井
  从水井中打水;  //活动
  V(mutex_well); //用完水井,释放资源
  P(mutex_vat);  //占用水缸
  将水倒入水缸中; //活动
  V(mutex_vat)  //释放资源
  V(pail);   //放桶
  V(full);  //full+1,即水缸中的水的桶数加1,注意成对出现问题!!!
 }
}
project old()//老和尚
{
 while (true)
 {
  P(full); //看是否有水,有则减一,程序向下执行
  P(pail); //拿桶
  P(mutex_vat); //占用水缸
  从水缸中取水; //活动
  V(mutex_vat); //用完水缸,释放资源
  喝水; //此处虽可省,但有这一步更为具体形象
  V(pail); //放桶
  V(empty); //容量加一,可装入的水的桶数加1
 }
}

通过看课本和在网上看一些别人的总结分享,我写出了这段伪代码。写完后心里还想着,咦,老师不是说比上次的题目困难很多吗?怎么这么容易就被我搞定了(是真的比较容易,只要求这样子的答案的话)。但,无论如何当时都觉得很开心,终于把这个每周都会有的令人担忧的操作系统实操作业搞定了!(毕竟我比较菜鸟)

2、再次上课

很多人都只是写了简单的一段伪代码,毕竟题目只是说算法描述,老师也没特别说明,就只是说回去把它搞好,So…
然后就被说了:”伪代码有啥可看的”,然后便"耐心“地讲解了关于这方面的内容,主要就是信号量的具体使用吧,下课前留了一句:回去接着把题目搞好啊…
课上老师讲解了一个简单的示例,如下:

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
HANDLE s;
int i = 100;
DWORD WINAPI fncc(LPVOID p)
 {
 DWORD id = GetCurrentThreadId();
 srand(id);
 while (true) 
 {
  int interval = int((double)rand() / RAND_MAX * 950 + 1050);//产生随机数并赋值
  printf("我是%d, 我在等待信号量\r\n", id);
  
  WaitForSingleObject(s, INFINITE);//等待信号量
  printf("我是%d, 我得到了信号量\r\n", id);
  printf("我是%d, i原来值是%d, 我把变量i赋值为 %d\r\n", id, i, interval);
  i = interval;
  Sleep(interval);
  printf("我是%d,之前我把i的值赋为 %d, 变量i现在的值是: %d\r\n", id, interval, i);
  ReleaseSemaphore(s, 1, NULL);//释放信号量
  
  printf("我是%d, 我释放了信号量\r\n", id);
 }
}
void main() 
{
 s = CreateSemaphore(NULL, 1, 1, NULL);
 DWORD threadId;
 
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 CreateThread(NULL, 4 * 1024, fncc, 0, 0, &threadId);
 
 while (!_kbhit())
 {
  Sleep(1000);
 }
}

这个示例我倒是明白,想说的是因为使用了 下面的语句

WaitForSingleObject(s, INFINITE);
代码语句;
  ReleaseSemaphore(s, 1, NULL);

所以,在一个线程休眠时,他给i赋的值在输出之前不会被另外一个线程更改。
运行结果如下图:
在这里插入图片描述

3、再解

这次想上网参考下别人的分享都不太行了,因为很少人分享这类问题的具体代码实现,分享个人分析这类问题和P、V操作的倒是很多,从极少的资源中学到的一点点东西写到VS又不行,这里行了那里又出问题,而且网上的比较零散,几乎没有完整的,也有一些这类问题完整的分享我却看不懂,虽然大概知道是怎么个过程,但写成代码一直在提示错误…
最后只好向同学请教了,代码整理分享如下:

#include"windows.h"
#include<iostream>
using namespace std;
/*用来打开或创建一个信号量,
HANDLE  CreateSemaphore(
 LPSECURTTY_ATTRIBUTES IpSemaphoreAttributes,//ID   属性,一般可设为NULL
 LONE IINitialCount,   //initial  count  信号量初始值,必须大于等于0,而且小于最大值
 LONG  IMaximumCount,  //maximum  count  设置信号量的最大值,必须大于0
 LPCTSTR IpName        //object  name    
 信号量的名字,可设置为NULL。IpName不为空时,如果当前信号量名与已存在的信号量的名字相同时,则该函数表示打开该信号量。
);
*/
//说明:下面的L"sem1"前的L有的电脑不需要加的,我的电脑要加了才可以,否则显示错误。
HANDLE  well= CreateSemaphore(NULL, 1, 1, L"sem1");//井的信号量        互斥量   初值=1,最大值=1  
HANDLE pail = CreateSemaphore(NULL, 3, 3, L"sem2");//小和尚老和尚用的水桶的信号量   初值=3,最大值=3
HANDLE vat = CreateSemaphore(NULL, 1, 1, L"sem4");//水缸的信号量          互斥量   初值=1,最大值=1     
HANDLE full = CreateSemaphore(NULL, 0, 10, L"sem5");//缸中可以取的水的桶数  即可取多少桶水。
HANDLE empty1 = CreateSemaphore(NULL, 10, 10, L"sem6");//缸中可以容纳的水桶数  即可容纳10桶水
int count1 = 0;  //此时水缸中存储水的桶数
CRITICAL_SECTION cs;
/*每个进程中访问临界资源的那段代码称为临界区,简称cs区(临界资源是一次仅允许一个进程使用的共享资源)。
每次只准许一个进程进入临界区,进入后不允许其他进程进入。
不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。*/


 /*WINAPI 视窗操作系统应用程序接口(Windows API),有非正式的简称法为WinAPI,
是微软对于Windows操作系统中可用的内核应用程序编程接口的称法。
它设计为由C/C++程序调用,而且它也是应用软件与Windows系统最直接的交互方式。
DWORD全称Double Word,是指注册表的键值,每个word为2个字节的长度,DWORD 双字即为4个字节,每个字节是8位,共32位。
 LPVOID是一个没有类型的指针,也就是说你可以将LPVOID类型的变量赋值给任意类型的指针,
 比如在参数传递时就可以把任意类型传递给一个LPVOID类型为参数的方法,
 然后在方法内再将这个“任意类型”从传递时的“LPVOID类型*/
 DWORD WINAPI youngFunc(LPVOID pArg) //小和尚打水入缸
{                                   
 int i = (int)pArg;          
 while (1)                  
 {     
  WaitForSingleObject(empty1, INFINITE);//判断水缸是否已满
  printf("%d号小和尚等待拿水桶\n", i);
  WaitForSingleObject(pail, INFINITE);//小和尚先取得水桶
  printf("%d号小和尚拿到了桶!\n", i);
  WaitForSingleObject(well, INFINITE);//小和尚先取得水井使用权,即占用水井
  printf("%d号小和尚打上了水\n", i);
  ReleaseSemaphore(well, 1, NULL);//小和尚用完水井,释放资源         
  /*1表示:这个信号量对象在当前基础上所要增加的值,这个值必须大于0,
如果信号量加上这个值会导致信号量的当前值大于信号量创建时指定的最大值,那么这个信号量的当前值不变,同时这个函数返回FALSE;
null,指向返回信号量上次值的变量的指针,如果不需要信号量上次的值,那么这个参数可以设置为NULL;返回值:
如果成功返回TRUE,如果失败返回FALSE,可以调用GetLastError函数得到详细出错信息;*/
  Sleep(1000);//休眠1s
  WaitForSingleObject(vat, INFINITE);//取得水缸的使用权,即占用水缸资源
  printf("\t\t%d号小和尚向缸中倒水\n", i);
  EnterCriticalSection(&cs);        
   //EnterCriticalSection(&cs);加锁 接下来的代码处理过程中不允许其他线程进行操作,除非遇到LeaveCriticalSection解锁                                   
  count1++;
  printf("\t\t缸中有%d桶\n", count1);
  LeaveCriticalSection(&cs);         //LeaveCriticalSection(&cs);解锁
  printf("\t\t缸中有%d桶\n", count1);
  LeaveCriticalSection(&cs);         
  //LeaveCriticalSection(&cs);解锁  到下一个EnterCriticalSection之间代码资源已经释放了,其他线程可以进行操作
  ReleaseSemaphore(vat, 1, NULL);//释放水缸资源
  EnterCriticalSection(&cs);      //加锁
  ReleaseSemaphore(pail, 1, NULL);//释放水桶
  printf("%d号小和尚放下了桶\n", i);
  LeaveCriticalSection(&cs);     //解锁   临界区
  ReleaseSemaphore(full, 1, NULL);//水缸中可使用的水的桶数加1
 }
 return NULL;
}


DWORD WINAPI oldFunc(LPVOID pArg)//老和尚取水饮用
{
 int i = (int)pArg;
 while (1)
 {
  Sleep(4000);
  WaitForSingleObject(full, INFINITE);//判断水缸中是否有水
  printf("\t\t\t\t\t%d号老和尚等待拿水桶\n", i);
  WaitForSingleObject(pail, INFINITE);//有则取得水桶
  printf("\t\t\t\t\t%d号老和尚拿到了桶\n", i);
  WaitForSingleObject(vat, INFINITE);//取得水缸的使用,占用
  printf("\t\t\t\t\t%d号老和尚喝到了水\n", i);
  EnterCriticalSection(&cs);      //加锁
  count1--;
  printf("\t\t缸中有%d桶\n", count1);
  LeaveCriticalSection(&cs);      //解锁
  ReleaseSemaphore(vat, 1, NULL);//释放水缸资源
  EnterCriticalSection(&cs);      //加锁
  ReleaseSemaphore(pail, 1, NULL);//释放水桶
  printf("\t\t\t\t\t\t\t%d号老和尚放下了桶\n", i);
  LeaveCriticalSection(&cs);      //解锁   临界区
  ReleaseSemaphore(empty1, 1, NULL);//水缸中可放水数量加1,即容量少了,可装入的水的桶数加1
 }
 return NULL;
}

int main()
{
 HANDLE young;
 HANDLE old;
 InitializeCriticalSection(&cs);  //临界区初始化
 int i = 0;
 while (true)
 {
  young = CreateThread(NULL, 0, youngFunc, LPVOID(i), NULL, NULL);  //创建子线程
  old = CreateThread(NULL, 0, oldFunc, LPVOID(i), NULL, NULL);
  i++;
 }
 HANDLE p[2];
 p[0] = young;
 p[1] = old;
 WaitForMultipleObjects(5, p, TRUE, INFINITE);
 WaitForMultipleObjects(5, p, TRUE, INFINITE);  
 /* WaitForMultipleObjects是Windows中的一个功能非常强大的函数,几乎可以等待Windows中的
 所有的内核对象(关于该函数的描述和例子见MSDN)。
    当WaitForMultipleObjects等到多个内核对象的时候,如果它的bWaitAll 参数设置为false。
    其返回值减去WAIT_OBJECT_0 就是参数lpHandles数组的序号。
    如果同时有多个内核对象被触发,这个函数返回的只是其中序号最小的那个。
     如果为TRUE 则等待所有信号量有效再往下执行。(FALSE 当有其中一个信号量有效时就向下执行)*/
      getchar();                  
/*getchar()函数的作用是从计算机终端(一般为键盘)获取一个无符号字符。
getchar()函数只能接收一个字符,其函数值就是从输入设备获取到的字符。*/
 return 0;
}

运行结果如图:
在这里插入图片描述
在这里插入图片描述
暂时就先分享到这里了,如有发现任何问题欢迎指正,或有什么改进之处还望不吝赐教,谢谢。
觉得可以的话给个小赞赞鼓励下呗!
有帮助?那就–>开启传送门 希望分享的东西对大家有所帮助哈!

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页