操作系统实验 1 银行柜员服务问题
操作系统实验一 银行柜员服务问题
实验报告
邵鑫
2016011111
2018 年 11 月 5 日
一、实验目的
1. 通过对进程间通信同步/互斥问题的编程实现,加深理解信号量和 P、V 操作的原理;
2. 对 Windows 或 Linux 涉及的几种互斥、同步机制有更进一步的了解;
3. 熟悉 Windows 或 Linux 中定义的与互斥、同步有关的函数。
二、实验内容
·问题描述:
银行有 n 个柜员负责为顾客服务,顾客进入银行先取一个号码,然后等着叫号。当某个
柜员空闲下来,就叫下一个号。
编程实现该问题,用 P、V 操作实现柜员和顾客的同步。
·实现要求:
1. 某个号码只能由一名顾客取得;
2. 不能有多于一个柜员叫同一个号;
3. 有顾客的时候,柜员才叫号;
4. 无柜员空闲的时候,顾客需要等待;
5. 无顾客的时候,柜员需要等待。
·实现提示:
1. 互斥对象:顾客拿号,柜员叫号;
2. 同步对象:顾客和柜员;
3. 等待同步对象的队列:等待的顾客,等待的柜员;
4. 所有数据结构在访问时也需要互斥。
第 1 页 共 9 页
操作系统实验 1 银行柜员服务问题
三、设计思路
①怎样输入输出?
输入输出数据的保存:使用结构体数组,非常方便
②如何模拟银行排队的过程?
对每一个柜员和顾客创建一个线程来模拟他们的行为。进程间通信使用 WINDOWS 提供的
同步和互斥量 semaphore 和 Mutex:创建同步信号量 sema,确保柜员处于等待状态,创建顾客
互斥量 cusmutex,防止多个顾客同时进入银行的时候拿到一样的号。创建柜员互斥量
servermutex,防止一个柜员服务多个顾客。
③进程间同步与互斥的操作顺序和逻辑关系?
·顾客进程睡眠相应时长模拟不同时间进入银行的过程
·顾客进入银行:
customermutex:P
·顾客取到号码:
sema:V
·柜台开始服务时:
servermutex:P
sema:P
·柜台结束服务:
servermutex:V
customermutex:V
四、程序结构与主要代码分析
程序结构基本如下
确定参数:最大服务顾客数和柜台数目
初始化
建立核心变量:待服务顾客数、已服务顾客数、顾客柜员编号
定义输入顾客、输出顾客、互斥/同步量数据类型
定义线程
main 函数
定义柜台服务线程
定义顾客排号线程
读入数据
初始化互斥/同步量
创建线程
运行线程,等待顾客服务完毕
关闭线程
输出结果
关键代码:
创建线程与控制退出:
for (int i = 1; i <= customer_number; i++)//创建顾客线程
CUS[i] = CreateThread(NULL, 0, PVcustomer, LPVOID(i), 0, NULL);
for (int j = 1; j <= SERVER; j++)//创建柜员线程
SER[j] = CreateThread(NULL, 0, PVserver, LPVOID(j), 0, NULL);
第 2 页 共 9 页
操作系统实验 1 银行柜员服务问题
while (customer_served != customer_number)//直到所有顾客均被服务
进程间同步与互斥:
DWORD WINAPI PVcustomer(PVOID PV)
{
}
int num = (int)PV;
customer_count++;
lineout[customer_count].number = linein[num].number;
lineout[customer_count].time_in = linein[num].time_in;
lineout[customer_count].time_need = linein[num].time_need;
Sleep(1000 * linein[num].time_in);//睡眠,直到进入
WaitForSingleObject(cusmutex, INFINITE);//这是取号
ReleaseMutex(cusmutex);//取完号就可以释放这个互斥量了
ReleaseSemaphore(sema, 1, NULL);//寻求柜员
return 0;
DWORD WINAPI PVserver(PVOID PV)
{
while (true)
{
WaitForSingleObject(sema, INFINITE);//等待顾客取号
WaitForSingleObject(servermutex, INFINITE);//等待柜员互斥量,占用柜台
ReleaseMutex(servermutex);//我考虑过使用互斥量数组,如果有柜员编号,应该在结束
后释放。如果只使用一个互斥量,在这里释放
server_number++;
int n = server_number;
time_t starttime = time(NULL);
Sleep(1000 * lineout[n].time_need);
time_t endtime = time(NULL);
lineout[n].time_begin = starttime - time_begin;
lineout[n].time_end = endtime - time_begin;
lineout[n].servernumber = (int)PV;
customer_served++;//服务完的顾客数目加一
}
return 0;
}
五、运行情况与结果分析
在运行通过了样例数据之后,为了体现互斥与同步的正确性,我构造了如下数据,其中
既有同时进入银行的顾客(考验顾客互斥量),也有同时开始服务的顾客(考验柜员互斥量)
测试数据如下表所示:
顾客编号
进入时间
服务时长
1
2
3
4
1
5
6
6
第 3 页 共 9 页
10
2
3
2
操作系统实验 1 银行柜员服务问题
6
7
7
8
9
10
3
10
3
2
1
1
5
6
7
8
9
10
首先令柜员数目为 3,运行结果如下:
表- 测试数据
图- 运行结果 1
很明显该结果是正确的,同时我们也看到,由于柜员数目较少,许多顾客等了一段时间,
9 号顾客整整等了 5 分钟。
修改柜员数目再次运行,这次令柜员数目为 5,运行结果如下图所示:
第 4 页 共 9 页
操作系统实验 1 银行柜员服务问题
可以看到,所有柜员都参与到工作中,同时非常幸运的是,没有顾客等待,他们都是拿
到号之后立刻找到了对应了柜员进行服务。
图- 运行结果 2
六、思考题解答
1. 柜员人数和顾客人数对结果分别有什么影响?
解:
在上面的运行结果中我们也分析了。
①柜员人数的影响:
主要影响的是总时长(即银行柜员什么时候可以下班,也就是最后一位顾客什么时候走)
和平均等待时间。其中平均等待时间这样计算:
number
(
t
customer
_
begin
t
average
_
waiting
i
1
t
in
)
customer
_
number
以我们在运行结果分析中使用的输入数据为例,改变柜员数目,得到如下结果:
平均等待时间/min
总时间/min
柜员人数
2
3
4
5
6
22
19
18
17
17
表- 思考题分析
5.3
2.6
0.9
0.2
0
可以明显看到,在顾客进入银行的时间和服务时长分布相同的情况下,柜员越多,做完
所有服务所需总时间长度越少,同时,顾客的平均等待时间越短。当柜员数目足够大时,再
增加柜员数目将不会显著减少总时长和平均等待时间。
②顾客人数的影响:如果我们固定柜员的人数,当顾客数目增大时,显然总时长会增加。
第 5 页 共 9 页
操作系统实验 1 银行柜员服务问题
如果单位时间内进入银行的顾客数目增大,那么顾客的平均等待时间会增大,这些都是显而
易见的。调试时对不同数据规模的测试数据的运行也说明了这一点。
2. 实现互斥的方法有哪些?各自有什么特点?效率如何?
优点
简单
方法
禁止中断
锁变量
严格轮转法
缺点
把禁止中断的权利交给用户进程,导
致系统可靠性较差;不适用于多处理
器
可能使两个进程同时处于临界区;忙
等待
有可能使临界区外的进程阻塞其他
进程;忙等待
PETERSON
解决了互斥访问问题,克服了强制
算法
轮转法缺点,可以正常工作
忙等待
硬件指令
方法
信号量
管程
适用于任意数目的进程;简单,易
验证其正确性;支持进程中存在多
个临界区
适用于任意数目的进程;解决忙等
待问题
提高代码可读性,便于修改和维护,
正确性易于保证
忙等待,耗费CPU时间
实现较复杂,需要系统调用;同步操
作分散,易读性较差,不利于修改和
维护,正确性难以保证
编译器必须识别管程并用某种方式
对其互斥作出安排;多数语言不支
持,不广泛
消息传递
适用于分布式系统
更加复杂
七、实验总结
表- 不同互斥方法的比较
本次实验的难点主要在于理解 WINDOWS 编程中的同步信号量和互斥信号量。由于有大量
陌生的函数和对象,使得我编程和调试的过程特别艰难。不过好在问题本身很有意思,这让
整个编程的过程充满了乐趣。通过这次实验,我学会了 WINDOW 中线程的基本使用方法,了
解了同步和互斥在 WINDOW 下如何实现,更加深入地理解了简单的进程间通信问题。
实验中还碰到了一些细碎的问题,
①我发现在不同的教程中,WINAPI 后面的参数既有 PVOID 也有 LPVOID,查阅资料之后
发现是 16 位 CPU 的历史遗留问题,LPVIOD 为长寻址,现在使用起来已经没有什么区别了。
②其实在创建线程的过程中,使用 CreatThread 并不是一种很好的方法,_beginthreade
-x 可能是一种更好的方法。不过这里不是实际项目,就使用 CreatThread 方便分析。
③WaitForMultipleObjects 真的是一个功能非常强大的函数,它可以在很多进程的应
第 6 页 共 9 页
操作系统实验 1 银行柜员服务问题
用下表示等待某个信号量。我在查询相关知识学习后,学会了最简单的应用。
调试过程出了以下问题,在我分析之后一一解决:
①由于用到了很多之前没有用过的函数和对象,我在设置参数的经常出毛病。比如对同
步信号量参数不熟悉,一开始把两个参数(0 和 MAX_N 定义反了)
②犯了两个非常愚蠢的错误,一是数组下标从 0 开始却到小于等于总数,导致输入实际
上多了一个;而是定义了宏 SEVER 之后,直接开了一个 SEVER 大小的静态数组,然后使用了
SER[SERVER],导致数组越界,使得计数变量从一个非常奇怪的数字开始(应该是内存被占
用了)
③一开始我定义了一个柜员互斥量数组,使用方法是:等待->使用->释放。后来想想觉
得没有必要就改成了使用一个柜员互斥量,但是忘记了这时候需要改变顺序,应该是等待->
释放->使用,结果运行结果发生了错误(即所有人都跑到一个柜员那去了),这让我苦恼了
很久,后来在线程中逐步输出当前柜员编号和服务顾客编号,我才发现问题在于释放的顺序
不正确。
附录 源代码
//Copyright by Shaoxin, Department of Electronic Engineering of Tsinghua University, China
#define SERVER 3//定义柜员数目
#define MAX_N 50//定义最大顾客数目,考虑到一般银行的规模,这里定义为 50
#include
#include
#include
#include
#include
#include
using namespace std;
HANDLE CUS[MAX_N+4], SER[SERVER+4];//注意扩大数组容量,防止溢出
int customer_served = 0;//已经服务过的顾客
int customer_number = 0;//顾客总数
int customer_count = 0;//顾客序号
int server_number = 0;//柜员序号
HANDLE sema = CreateSemaphore(NULL, 0, MAX_N, NULL);//同步信号量,确保柜员处于等待状态
HANDLE cusmutex = CreateMutex(NULL, FALSE, NULL);//顾客互斥量,防止多个顾客同时进入银行
的时候拿到一样的号
HANDLE servermutex = CreateMutex(NULL, FALSE, NULL);//柜员互斥量,防止一个柜员服务多个顾
客
struct customer//顾客信息结构体
{
};
int number;
int time_in;
int time_need;
struct printinfo//输出信息结构体
{
int number;
第 7 页 共 9 页
操作系统实验 1 银行柜员服务问题
int time_in;
int time_begin;
int time_end;
int servernumber;
int time_need;
};
time_t time_begin = time(NULL);//开始计时
customer linein[MAX_N];//储存输入顾客信息
printinfo lineout[MAX_N];//储存输出顾客信息
DWORD WINAPI PVcustomer(PVOID PV)
{
}
int num = (int)PV;
customer_count++;
lineout[customer_count].number = linein[num].number;
lineout[customer_count].time_in = linein[num].time_in;
lineout[customer_count].time_need = linein[num].time_need;
Sleep(1000 * linein[num].time_in);//睡眠,直到进入
WaitForSingleObject(cusmutex, INFINITE);//这是取号
ReleaseMutex(cusmutex);//取完号就可以释放这个互斥量了
ReleaseSemaphore(sema, 1, NULL);//寻求柜员
return 0;
DWORD WINAPI PVserver(PVOID PV)
{
while (true)
{
WaitForSingleObject(sema, INFINITE);//等待顾客取号
WaitForSingleObject(servermutex, INFINITE);//等待柜员互斥量,占用柜台
ReleaseMutex(servermutex);//我考虑过使用互斥量数组,如果有柜员编号,应该在结束
后释放。如果只使用一个互斥量,在这里释放
server_number++;
int n = server_number;
time_t starttime = time(NULL);
Sleep(1000 * lineout[n].time_need);
time_t endtime = time(NULL);
lineout[n].time_begin = starttime - time_begin;
lineout[n].time_end = endtime - time_begin;
lineout[n].servernumber = (int)PV;
customer_served++;//服务完的顾客数目加一
}
return 0;
}
int main()
{
第 8 页 共 9 页