操作系统课程设计报告
一、操作系统课程设计任务书
读者-写者问题实现
1 设计目的
通过实现经典的读者写者问题,巩固对线程及其同步机制的学习效果,
加深对相关基本概念的理解,并学习如何将基本原理和实际设计有机的结
合。
2 设计内容
在 Windows 2000/XP 环境下,使用多线程和信号量机制实现经典的读
者写者问题,每个线程代表一个读者或一个写者。每个线程按相应测试数
据文件的要求,进行读写操作。请用信号量机制分别实现读者优先和写者
优先的读者-写者问题。
读者-写者问题的读写操作限制:
(1)写-写互斥,即不能有两个写者同时进行写操作
(2)读-写互斥,即不能同时有一个读者在读,同时却有一个写者在写
(3)读-读允许,即可以有二个以上的读者同时读
读者优先的附加限制:如果一个读者申请进行读操作时已有另一读者
正在进行读操作,则该读者可直接开始读操作。
写者优先的附加限制:如果一个读者申请进行读操作时已有另一写者
在等待访问共享资源,则该读者必须等到没有写者处于等待状态后才能开
始读操作。
运行结果显示要求:要求在每个线程创建、发出读写操作申请、开始
读写操作和结束读写操作时分别显示一行提示信息,以确信所有处理都遵
守相应的读写操作限制。
3 测试数据文件格式
测试数据文件包括 n 行测试数据,分别描述创建的 n 个线程是读者还
是写者,以及读写操作的开始时间和持续时间。每行测试数据包括四个字
段,各字段间用空格分隔。第一字段为一个正整数,表示线程序号。第二
字段表示相应线程角色,R 表示读者是,W 表示写者。第三字段为一个正
数,表示读写操作的开始时间。线程创建后,延时相应时间(单位为秒)
后发出对共享资源的读写申请。第四字段为一个正数,表示读写操作的持
续时间。当线程读写申请成功后,开始对共享资源的读写操作,该操作持
续相应时间后结束,并释放共享资源。下面是一个测试数据文件的例子:
1 R 3 5
2 W 4 5
3 R 5 2
4 R 6 5
5 W 5.1 3
4 相关 API 函数
CreateThread()在调用进程的地址空间上创建一个线程
ExitThread()用于结束当前线程
Sleep()可在指定的时间内挂起当前线程
CreateMutex()创建一个互斥对象,返回对象句柄
OpenMutex()打开并返回一个已存在的互斥对象句柄,用于后续访
问
ReleaseMutex()释放对互斥对象的占用,使之成为可用
WaitForSingleObject()可在指定的时间内等待指定对象为可用
状态
InitializeCriticalSection()初始化临界区对象
EnterCriticalSection()等待指定临界区对象的所有权
LeaveCriticalSection()释放指定临界区对象的所有权
二、设计思路
将所有的读者和所有的写者分别放进两个等待队列中,当
读允许时就让读者队列释放一个或多个读者,当写允许时,释
放第一个写者操作。
读者优先:
如果没有写者正在操作,则读者不需要等待,用一个整
型变量 readcount 记录当前的读者数目,用于确定是否释放写
者线程,(当 readcout=0 时,说明所有的读者都已经读完,
释 放 一 个 写 者 线 程 ), 每 个 读 者 开 始 读 之 前 都 要 修 改
readcount,为了互斥的实现对 readcount 的修改,需要一个互
斥对象 Mutex 来实现互斥。
另外,为了实现写-写互斥,需要一个临界区对象 write,
当写者发出写的请求时,必须先得到临界区对象的所有权。
通过这种方法,可以实现读写互斥,当 readcount=1 时,(即
第一个读者的到来时,),读者线程也必须申请临界区对象的
所有权.
当读者拥有临界区的所有权,写者都阻塞在临界区对象
write 上。当写者拥有临界区对象所有权时,第一个判断完
readcount==1 后,其余的读者由于等待对 readcount 的判断,
阻塞在 Mutex 上!
写者优先:
写者优先和读者优先有相同之处,不同的地方在:一旦
有一个写者到来时,应该尽快让写者进行写,如果有一个写
者在等待,则新到的读者操作不能读操作,为此添加一个整
型变量 writecount,记录写者的数目,当 writecount=0 时才可以
释放读者进行读操作! 为了实现对全局变量 writecount 的互
斥访问,设置了一个互斥对象 Mutex3。
为了实现写者优先,设置一个临界区对象 read,当有写者在写
或等待时,读者必须阻塞在临界区对象 read 上。
读者除了要一个全局变量 readcount 实现操作上的互斥
外,还需要一个互斥对象对阻塞在 read 这一个过程实现互
斥,这两个互斥对象分别为 mutex1 和 mutex2。
程序结构
主要代码进行分析:
1、临界区:
CRITICAL_SECTION RP_Write; //临界区
CRITICAL_SECTION cs_Write;
CRITICAL_SECTION cs_Read;
临界区(Critical Section)是一段独占对某些共享资源访问的代码,
在任意时刻只允许一个线程对共享资源进行访问。如果有多个线
程试图同时访问临界区,那么在有一个线程进入后其他所有试图
访问此临界区的线程将被挂起,并一直持续到进入临界区的线程
离开。临界区在被释放后,其他线程可以继续抢占,并以此达到
用原子方式操作共享资源的目的。
临界区在使用时以 CRITICAL_SECTION 结构对象保护共享
资源,并分别用 EnterCriticalSection()和 LeaveCriticalSection()
函数去标识和释放一个临界区。所用到的 CRITICAL_SECTION
结构对象必须经过 InitializeCriticalSection()的初始化后才能使
用,而且必须确保所有线程中的任何试图访问此共享资源的代码
都处在此临界区的保护之下。否则临界区将不会起到应有的作用,
共享资源依然有被破坏的可能。
2、定义线程结构:
struct ThreadInfo
{
int Threadhao;
char ThreadClass;
double ThreadStartTime;
double ThreadRunTime;
};
此结构用来存放线程的信息,四个成员变量依次表示线程序号、线
程类别、线程开始时间、线程读写持续时间。
3、互斥对象
创建互斥对象
CreateMutex(NULL,FALSE,"mutex_for_readcount");
参数含义如下:
NULL表示创建带有默认安全性的内核对象
FALSE表示该互斥对象没有被任何线程所拥有
mutex_for_readcount是为内核对象赋予名字。
释放互斥信号
ReleaseMutex(h_Mutex);
对资源具有访问权的线程不再需要访问此资源而要离开时,必
须通过ReleaseMutex()函数来释放其拥有的互斥对象
4、创建读者线程
CreateThread(NULL,0,\(LPTHREAD_START_ROUTINE)(R_ReaderTh
read),
\&thread_info[i],0,&thread_ID);
参数含义如下:
NULL表示创建带有默认安全性的内核对象
0表示新读者线程拥有自己的堆栈,使用缺省大小:1MB。
(LPTHREAD_START_ROUTINE)(R_ReaderThread)表示新读
者线程执行的线程函数的地址
&thread_info[i]表示在线程启动执行时将该参数传递给
读者线程函数。
0表示读者线程创建后可以立即进行调度
&thread_ID表示CreateThread使用这个地址来存放系统
分配
5、等待函数
给新读者线程的I D
WaitForMultipleObjects(n_thread,h_Thread,TRUE,-1);
等待函数可使线程自愿进入等待状态,直到一个特定的内核对象
变为已通知状态为止
参数含义如下:n_thread表示线程数量。
h_Thread是指向线程对象句柄的数组的指针。
ture表示:在所有线程对象变为已通知状态之前,该函数将不
允许调用线程运行
参数 -1 告诉系统,调用线程愿意永远等待下去(无限时间
量),直到该进程终止运行。
三、运行结果
程序运行结果如下:
请选择要进行的操作:
1 (回车)
1 (回车)
请选择要进行的操作:
2 (回车)
四、设计总结
本次操作系统课程设计完成的是读者-写者问题,通过学习对
线程及其同步机制有了很的学习和掌握. 并认识到同步可以保证
在一个时间内只有一个线程对某个资源有控制权。共享资源包括
全局变量、公共数据成员或者句柄等。同步还可以使得有关联交
互作用的代码按一定的顺序执行。同时也掌握了实现线程同步的
对象有 Critical_section(关键段),Event(事件),Mutex(互
斥对象),Semaphores(信号量)并对这些对象理解如下:
对于关键段对象:首先,定义一个关键段对象;然后,初始化
该对象。初始化时把对象设置为 NOT_SINGALED,表示允许线程使
用资源:如果一段程序代码需要对某个资源进行同步保护,则这
是一段关键段代码。在进入该关键段代码前调用
EnterCriticalSection 函数,这样,其他线程都不能执行该段代
码,若它们试图执行就会被阻塞。完成关键段的执行之后,调用
LeaveCriticalSection 函数,其他的线程就可以继续执行该段代
码。如果该函数不被调用,则其他线程将无限期的等待。
对于互斥对象 首先,调用 CreateMutex 创建互斥对象;然后,
调用等待函数,可以的话利用关键资源;最后,调用 RealseMutex
释放互斥对象。互斥对象可以在进程间使用,但关键段对象只能
用于同一进程的线程之间。
对于等待函数:等待函数有:1.等待单个对象的(FOR SINGLE
OBJECT): WaitForSingleObject,函数参数包括同步对象的句柄
和等待时间等。如果等待时间不限制(Infinite),则只有同步对
象获得信号才返回;如果等待时间为 0,则在测试了同步对象的
状态之后马上返回。2.等待多个对象的(FOR MULTIPLE OBJECTS)
WaitForMultipleObjects,函数参数包括同步对象的句柄,等待时
间,是等待一个还是多个同步对象等等,如果等待时间不限制
(Infinite),则只有同步对象获得信号才返回;如果等待时间为
0,则在测试了同步对象的状态之后马上返回。
操作系统设计使我对 C++有了一个很好的复习和学习,对线程
的创建及同步问题及其同步对象都有了很好的理解。并能初步进