§1.1第一题实验目标
题目场景:
某外语考试,主考在考场对10名应试者进行考核。考场内部每次只能接待1名考生。在考场外部的走廊里,有5把椅子,有1名助理对坐在椅子上的考生进行叫号。
规则是:
1.考生需要首先坐在走廊里的椅子上按照顺序等待,如果没有空椅子,只能等其他考生腾出椅子。
2.助理负责从坐在椅子上排队等候的考生中顺序引领一名考生进入考场;如果没有人等待,助理等候;如果考场中已有考生应考,则须等当前考生结束考试后才能引领下一位考生。
3.当主考考核完一名考生后,考生方可离开;同时,考生等待助理引领下一位考生进入;如果没有考生进入,主考等待。
实验内容:
若采用Linux操作系统,利用pthread API提供的信号量机制,编写应用程序实现该问题的解决方案。(Windows亦有相应的解决方案)
可在一个进程里完成:从1个主线程开始,为每位考生生成1个线程(代表考生的动作),为主考、助理分别生成由1个线程(分别代表主考、助理的动作)。
通过在程序中合适的位置设置打印信息,打印到屏幕上或文本文件中,以显示程序的正确执行过程。
题目中的考核操作:可用sleep函数模拟(让程序sleep一段时间,3~5秒钟为宜)。
要求:输出内容清晰明了,输出格式简单统一,能够很直观地看出是那个线程输出的什么内容。
§1.2 第一题原理、方法、过程
1.2.1 题目分析
对于本题,首先我们可以看到存在3类实体,分别为主考,助理,考生。其行为分别如下。
(1)主考:等待助理将考生带入场;对考生进行考试
(2)助理:等待考生进入椅子;带领考生进考场;等候考生结束考试
(3)考生:等待空椅子;等待助理
不难发现,我们需要建立三类进程,分别为主考1,助理1,考生*10。
接下来需要对共享资源进行分析,可以发现存在两个共享资源,大小为5的缓存代表椅子的空位,以及大小为1的缓存用来代表可用的助理人数。
1.2.2 设计思想
(1)考生间存在互斥,需要对五把椅子形成的缓存区进行竞争。
(2)考生在排队时与助理线程间存在同步,此时考生与助理间存在一个可变共享缓存区问题。
(3)助理叫号时,需要和主考线程同步,此时助理线程和主考线程之间构成一个单一缓冲区共享问题。通过助理人数的缓存进行。
(4)助理需要等待考生考完才能进行下一次叫号,存在一个合作进程间执行次序的问题。
(5)为了保证进程间竞争,设计一个惊群过程,使10个考生进程同时竞争。需要一个惊群信号量,在考生进程都建立后在释放10个,释放过程加锁保护。
(6)为了使助理对考生按序叫号,缓存使用队列结构,队列的进出需要加锁保护
1.2.3信号量设计
对于本题,存在以下信号量
Sc=5; //椅子空余数
Sa=1; //助教空余数
M1=0; //椅子上人数
M2=0 ; //待考人数
Panic=0; //惊群
Mtx=0; //互斥锁
1.2.4 流程图设计
(1)考生流程图
(2)助理流程图
(3)主考
1.2.5 伪代码设计
int main()
{
int Sc=5; //椅子空余数
int Sa=1; //助教空余数
int M1=0; //椅子上人数
int M2=0 ; //待考人数
int Mtx=0; //互斥锁
cbegin()
主考();
助理();
For i=1 to 10 do
学生i();
cend()
}
主考()
{
While(1)
{
P(M2);
考核;
V(Sa);
}
}
助理()
{
While(1)
{
P(Sa);
P(M1);
带考生进场;
V(Sc);
V(M2);
}
}
学生()
{
P(Sc);
入座等待;
V(M1);
}
§1.3 第一题实验结果(关键的真实代码、截图、结果等)
1.3.1 代码展示(windows,C++)
#include <iostream>
#include<ctime>
#include<thread>
#include<mutex>
#include<windows.h>
#include<queue>
#include<map>
using namespace std;
CRITICAL_SECTION g_csVar; //创建关键段cs
HANDLE Sc; //椅子空余数
HANDLE Sa; //助教空余数
HANDLE M1; //椅子上人数
HANDLE M2 ; //待考人数
HANDLE panic; //惊群信号
mutex mtx; //互斥锁
HANDLE test; //考试是否结束
int Num=-1; //结束考试的考生编号
queue<thread::id> ID;
map<thread::id,int> m;
void M() //主考进程
{
int i=0;
while (i++<10) { //一共进行10次考核
long sa = 0,n=0;
WaitForSingleObject(M2, INFINITE);
mtx.lock();
thread::id i = ID.front();
cout << "考生"<<m[i]<<"已入场考试" << endl << "考试中" << endl;
mtx.unlock();
Sleep(1000);
mtx.lock(); //加锁保护缓存
cout << "考试结束" << endl;
ID.pop(); //将考生号从缓存中弹出
mtx.unlock();
ReleaseSemaphore(test, 1, NULL);
Sleep(100); //防止考生收到信号离场前下一个考生入场
mtx.lock();
if (ID.empty())
{
cout << "所有考生已考完" << endl;
}
else
{
cout << "等待下一个考生" << endl;
}
mtx.unlock();
ReleaseSemaphore(Sa, 1, &sa);
}
}
void Ad() //助理进程
{
int i = 0;
while (i++<10) {
long sc = 0;
long m2 = 0;
WaitForSingleObject(Sa, INFINITE);
WaitForSingleObject(M1, INFINITE);
mtx.lock();
thread::id i = ID.front();
cout << "助教引领考生"<<m[i]<<"入场" << endl;
mtx.unlock();
ReleaseSemaphore(Sc, 1, &sc);
ReleaseSemaphore(M2, 1, &m2);
}
}
void Stu()
{
WaitForSingleObject(panic, INFINITE); //等待惊群信号
WaitForSingleObject(Sc, INFINITE);
mtx.lock(); //加锁保护缓存区
thread::id i= this_thread::get_id();
ID.push(i); //将进程号压入队列
cout << "考生"<<m[i]<<"入座等待" << endl;
mtx.unlock();
long m1;
ReleaseSemaphore(M1, 1, &m1);
while (1) { //考生等待考试结束信号
WaitForSingleObject(test, INFINITE);
mtx.lock();
if (Num==m[i]) //查看离场考生
{
cout << "考生" << Num << "离场" << endl;
mtx.unlock();
break;
}
mtx.unlock();
ReleaseSemaphore(test, 1, NULL);
}
}
int main()
{
InitializeCriticalSection(&g_csVar);
/*申请信号量*/
Sc = CreateSemaphoreA(NULL, 5, 5, "NumOfChair");
Sa= CreateSemaphoreA(NULL, 1, 1, "NumOfAd");
M1= CreateSemaphoreA(NULL, 0, 5, "NumOfS1");
M2 = CreateSemaphoreA(NULL, 0, 1, "NumOfS2");
panic = CreateSemaphoreA(NULL, 0, 10,NULL);
/*建立进程*/
thread *stu[10];
thread t1(M);
thread t2(Ad);
thread::id id_;
for (int i = 0; i < 10; i++)
{
stu[i] = new thread(Stu);
id_ = stu[i]->get_id();
m[id_] = i+1;
}
/*释放惊群*/
long l;
mtx.lock();
ReleaseSemaphore(panic, 10, &l);
mtx.unlock();
/*链接进程*/
for (int i = 0; i < 10; i++)
{
stu[i]->join();
}
t1.join();
t2.join();
CloseHandle(Sc); //销毁信号量
CloseHandle(Sa);
CloseHandle(M1);
CloseHandle(M2);
CloseHandle(panic);
DeleteCriticalSection(&g_csVar);
}
1.3.2 学生入场顺序
学生1 学生5 学生3 学生4 学生2 学生6 学生7 学生8 学生9 学生10
1.3.3实验截图