您好,欢迎来到爱站旅游。
搜索
您的当前位置:首页读者-写者 操作系统实验报告 计算机操作系统

读者-写者 操作系统实验报告 计算机操作系统

来源:爱站旅游
4.1实验二:读者写者问题 4.1.1实验要求

在Windows 环境下,创建一个控制台进程,此进程包含n个线程。用这n个线程来表示n个读者或写者。每个线程按相应测试数据文件(后面有介绍)的要求进行读写操作。用信号量机制分别实现读者优先和写者优先的读者-写者问题。

读者-写者问题的读写操作限制(包括读者优先和写者优先): 1) 写-写互斥,即不能有两个写者同时进行写操作。

2) 读-写互斥,即不能同时有一个线程在读,而另一个线程在写。 3) 读-读允许,即可以有一个或多个读者在读。

读者优先的附加限制:如果一个读者申请进行读操作时已有另一个读者正在进行读操作,则该读者可直接开始读操作。

写者优先的附加限制:如果一个读者申请进行读操作时已有另一写者在等待访问共享资源,则该读者必须等到没有写者处于等待状态才能开始读操作。

运行结果显示要求:要求在每个线程创建、发出读写操作申请、开始读写操作和结果读写操作时分别显示一行提示信息,以确定所有处理都遵守相应的读写操作限制。 4.1.2测试数据文件格式

测试数据文件包括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.1.3实习分析

可以将所有读者和所有写者分别存于一个读者等待队列和一个写者等待队列中,每当读允许时,就从读者队列中释放一个或多个读者线程进行读操作;每当写允许时,就从写者队列中释放一个写者进行写操作。

1.读者优先

读者优先指的是除非有写者在写文件,否则读者不需要等待。所以可以用一个整型变量read-count记录当前的读者数目,用于确定 是否需要释放正在等待的写者线程(当read-count=0时,表明所有的读者读完,需要释放写者等待队列中的一个写者)。每一个读者开始读文件时,必须修改read-count变量。因此需要一个互斥对象mutex来实现对全局变量read-count修改时的互斥。

另外,为了实现写-写互斥,需要增加一个临界区对象write。当写者发出写请求时,必须申请临界区对象的所有权。通过这种方法,也可以实现读-写互斥,当read-count=1时(即第一个读者到来时),读者线程也必须申请临界区对象的所有权。

当读者拥有临界区的所有权时,写者阻塞在临界区对象write上。当写者拥有临界区的所有权时,第一个读者判断完“read-count==1”后阻塞在write上,其余的读者由于等待对read-count的判断,阻塞在mutex上。

2.写者优先

写者优先与读者优先类似。不同之处在于一旦一个写者到来,它应该尽快对文件进行写操作,如果有一个写者在等待,则新到来的读者不允许进行读操作。为此应当添加一个整型变量write-count,用于记录正在等待的写者数目,当write-count=0时,才可以释放等待的读者线程队列。

为了对全局变量write-count实现互斥,必须增加一个互斥对象mutex3。

为了实现写者优先,应当添加一个临界区对象read,当有写者在写文件或等待时,读者必须阻塞在read上。

读者线程除了要对全局变量read-count实现操作上的互斥外,还必须有一个互斥对象对阻塞read这一过程实现互斥。这两个互斥对象分别命名为mutex1和mutex2。 4.1.4相关API函数说明

1.CreateThread 函数功能:

该函数创建一个在调用进程的地址空间中执行的线程。 函数原型:

HANDLE CreateThread(LPSECURITY-ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD-START-TOUTINE lpStartAddress,

LPVOID lpParameter, DWORD dwCreationFlags, LLPDWORD lpThreadId); 参数:

·lpThreadAttributes:指向一个SECURITY-ATTRIBUTES结构,该结构决定了返回的句柄是否可被子进程继承。若lpThreadAttributes为NULL,则句柄不能被继承。 在Windows NT中该结构的lpSwcurityDescriptor成员定义了新进程的安全性描述符。若lpThreadAttributes为NULL,则线程获得一个默认的安全性描述符。 ·dwStackSize:定义原始堆栈提交时的大小(按字节计)。系统将该值舍入为最近的页。若该值为0,或小于默认时提交的大小,默认情况是使用与调用线程同样的大小。更多的信息,请看Thread Stack Size。

·lpStartAddress:指向一个LPTHREAD-START-TOUTINE类型的应用定义的函数,该线程执行此函数。该指针还表示远程进程中线程的起始地址。该函数必须存在于远程进程中。

·lpParameter:定义一个传递给该进程的32位值。

·dwCreationFlags:定义控制进程创建的附加标志。若定义CREATE-SUSPENDED标志,线程创建时处于挂起状态,并且直到ResumeThread函数调用时才能运行。若该值为0,则该线程在创建后立即执行。

·lpThreadId:指向一个32位值,它接收该线程的标识符。 返回值:

若函数调用成功,返回值为新线程的句柄;若函数调用失败,返回值为NULL。 备注:

新线程的句柄创建时设为THREAD-ALL-ACCESS访问权限。若未提供安全性描述符,则该句柄可被任何要求一个线程对象句柄的函数所使用。若提供了安全性描述符,则以后使用该句柄时,将在授权访问以前执行访问检查。若访问检查被拒绝访问,则请求进程不能使用该句柄获得对该线程的访问。

线程从lpStartAddress参数定义的函数处开始执行。若该函数返回,系统将默认地认为以调用ExitThread函数的方法终止该线程。使用GetExitCodeThread 函数来获得线程的返回值。

线程创建时拥有THREAD-PRIORITY-NORMAL优先权。使用GetThreadPriority和SetThreadPriority函数可以获得和设置线程的优先权值。

一个线程终止时,该线程对象被设为发信号状态,以满足在该对象上等待的所有进程。 一个线程对象始终存在于系统中,直到该线程终止,且它所有的句柄都已通过调用CloseHandle函数关闭。

2.ExitThread 函数功能:

该函数结束一个线程。 函数原型:

VOID ExitThread(DWORD dwExitcode); 参数:

·dwExitcode:定义调用线程的退出代码。使用GetExitcodeThread函数来检测一个线程的退出代码。 返回值:无。 备注:

调用ExitThread函数,是结束一个线程的较好的方法。调用该函数后(或者直接地调用,或者从一个线程过程返回),当前线程的堆栈取消分配,线程终止。若调用该函数时,该线程为进程的最后一个线程,则该线程的进程也被终止。

线程对象的状态变为发信号状态,以释放所有正在等待该线程终止的其他线程。线程的终止状态从STILL-ACTIVATE变为dwExitcode参数的值。

线程结合时不必从操作系统中移去该线程对象。当线程的最后一个句柄关闭时,该线程对象被删除。

3.SLEEP 函数功能:

该函数对于指定的时间间隔挂起当前的执行线程。 函数原型:

VOID SLEEP(DWORD dwMilliseconds); 参数:

·dwMilliseconds:定义挂起执行线程的时间,以毫秒(ms)为单位。取值为0时,该线程将如余下的时间片交给处于就绪状态的同一优先级的其他线程。若没有处于就绪状态的同一优先级的其他线程,则函数立即返回,该线程继续执行。若取值为INFINITE则造成无限延迟。 返回值:

该函数没有返回值。 备注:

一个线程可以在调用该函数时将睡眠时间设为0ms,以将剩余的时间片交出。 4.CreateMutex 函数功能:

该函数创建有名或者无名的互斥对象。 函数原型:

HANDLE CreateMutex (LPSECURITY-ATTRIBUTES lpMutexAttributes,

BOOL bInitialOwner,LPCTSTR lpName); 参数:

·lpMutexAttributes:指向SECURITY-ATTRIBUTES结构的指针,该结构决定子进程是否能继承返回句柄。如果lpMutexAttributes为NULL,那么句柄不能被继承。

在Windows NT中该结构的lpSwcurityDescriptor成员指定新互斥对象的安全性描述符。若lpThreadAttributes为NULL,那么互斥对象获得默认的安全性描述符。 ·bInitialOwner:指定互斥对象的初始所属身份。如果该值为TRUE,并且调用者创建互斥对象,那么调用线程获得互斥对象所属身份。否则,调用线程不能获得互斥对象所属身份。判断调用者是否创建互斥对象请参阅返回值部分。

·lpName:指向以NULL结尾的字符串,该字符串指定了互斥对象名。该名字的长度大于MAX-PATH且可以包含除反斜线(\\)路径分隔符以外的任何字符。名字是区分大小写的。

如果lpName与已存在的有名互斥对象相匹配,那么该函数要求用MUTEX-ALL-ACCESS权限访问已存在的对象。在这种情况下,由于参数bInitialOwner已被创建进程所设置,该参数被忽略。如果参数lpMutexAttributes不为NULL,它决定句柄是否解除继承,但是其安全描述符成员被忽略。

如果lpName为NULL,那么创建的互斥对象无名。

如果lpName与已存在的事件、信号量、可等待定时器、作业或者文件映射对象的名字相匹配,那么函数调用失败,并且GetLastError函数返回ERROR-INVALID-HANDLE,其原因是这些对象共享相同的名字空间。 返回值:

如果函数调用成功,返回值为互斥对象句柄;如果函数调用之前,有名互斥对象已存在,那么函数给已存在的对象返回一个句柄,并且函数GetLastError返回ERROR-ALREADY-EXISTS,否则,调用者创建互斥对象。

如果函数调用失败,则返回值为NULL。若想获得更多错误信息,请调用GetLastError函数。

备注:

由函数CreateMutex返回的句柄有MUTEX-ALL-ACCESS权限可以去访问新的互斥对象,并且可用在请求互斥对象句柄的任何函数中。

调用进程中的任何线程可以可以在调用等待函数时指定互斥对象句柄。当指定对象的状态为信号态时,返回单对象等待函数。当任何一个或者所有的互斥对象都为信号态时,返回多对象等待函数指令。等待函数返回后,等待的线程被释放,继续向下执行。

当一个互斥对象不被任何线程拥有时,处于信号态。创建该对象的线程可以使用bInitialOwner标志来请求立即获得对该互斥对象的所有权。否则,线程必须使用等待函数来请求所有权。当互斥对象处于信号态,等待的线程获得对该对象的所有权时,此互斥对象的状态被设置为非信号态,等待函数返回。任意时刻,仅有一个线程能拥有该互斥对象,线程可以使用ReleaseMutex函数来释放对这个互斥对象的所有权。

若线程已经拥有了一个互斥对象,那么它可以重复调用等待函数而不会发生阻塞,一般情况下,用户不会重复等待同一个互斥对象,这种机制防止了线程因等待它已经拥有的互斥对象而发生死锁。然而,线程必须为每一次等待调用一次ReleaseMutex函数来释放该互斥对象。

两个或多个互斥进程可以调用CreateMutex来创建同名的互斥对象,第一个进程实际创建互斥对象,以后的进程打开已存在的互斥对象的句柄。这使得多个进程可以得到同一个互斥对象的句柄,从而减轻了用户的负担,使用户不必判断创建进程是否为第一个启动的进程。

使用这种技术时,应该把bInitialOwner标志设为FALSE;否则很难确定开始时哪一个进程拥有该互斥对象。

由于多进程能够拥有相同互斥对象的句柄,通过使用这个对象,可使多进程同步。以下为共享对象机制:

·如果CreateMutex中的lpMutexAttributes参数允许继承,由CreateProcess函数创建的子进程可以继承父进程的互斥对象句柄。

·一个进程可以在调用DuplicateHandle函数时指定互斥对象句柄来创建一个可以被其他进程使用的双重句柄。一个进程在调用OpenMutex或CreateMutex函数时能指定互斥对象名。

·使用CloseHandle函数关闭句柄,进程时系统自动关闭句柄。当最后一个句柄被关闭时,互斥对象被销毁。

5.ReleaseMutex 函数功能:

该函数放弃指定互斥对象的所有权。 函数原型:

BOOL ReleaseMutex(HANDLE hMutex); 参数:

·hMutex:互斥对象句柄。为CreateMutex或OpenMutex函数的返回值。 返回值:

如果函数调用成功,那么返回值是非零值;如果函数调用失败,那么返回值是零值。若想获得更多错误信息,请调用GetLastError函数。

备注:

如果调用线程不拥有互斥对象,ReleaseMutex函数失败。 一个线程通过调用等待函数拥有互斥对象。创建该互斥对象的线程也拥有互斥对象,而不需要调用等待函数。当互斥对象的所有者线程不再需要互斥对象时,它可以调用ReleaseMutex函数。

当一个线程拥有一个互斥对象后,它可以用该互斥对象多次调用等待函数而不会阻塞。这防止一个线程等待一个它拥有的互斥对象时出现死锁。不过,为了释放所有权,该线程必须为每一个等待操作调用一次ReleaseMutex函数。

6.WaitForSingleObject 函数功能:

当下列情况之一发生时该函数返回:(1)指定对象处于信号态;(2)超时。 函数原型:

DWORD waitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds); 参数:

·hHandle:等待对象句柄。若想了解指定句柄的对象类型列表,参阅下面备注部分。 在Windows NT中,句柄必须有SYNCHRONIZE访问权限。若想获得更多的信息,请查看Standard Access Rights。

·dwMilliseconds:指定以毫秒为单位的超时间隔。如果超时,即使对象的状态是非信号态的并且没有完成,函数也返回。如果dwMilliseconds是0,函数测试对象的状态并立即返回;如果dwMilliseconds是INFINITE,函数从不超时。 返回值:

如果函数调用成功,返回值表明引起函数返回的事件。可能值如下:

·WAIT-ABANDONED:指定对象是互斥对象,在线程被终止前,线程没有释放互斥

对象。互斥对象的所属关系被授予调用线程,并且该互斥对象被置为非信号态。 ·WAIT-OBJECT-0:指定对象的状态被置为信号态。

·WAIT-TIMEOUT:超时,并且对象的状态为非信号态。

如果函数调用失败,返回值是WAIT-FAILED。若想获得更多错误信息,请调用GetLastError函数。

备注:

waitForSingleObjects函数决定等待条件是否被满足。如果等待条件并没有被满足,调用线程进入一个高效的等待状态,当等待满足条件时占用非常少的处理机时间。

在运行前,一个等待函数修改同步对象类型的状态。修改仅发生在引起函数返回的对象身上。例如,信号的计数减1。

WaitForSingleObjects函数能等待的对象包括:Change notification(改变通告);Consoleinput(控制台输入);Event(事件);Job(作业);Mutex(互斥对象);Process(进程);Semaphore(信号量);Thread(线程);Waitable timer(可等待定时器)。

当使用等待函数或代码直接或间接创建窗口时,一定要小心。如果一个线程创建了任何窗口,它必须处理进程消息。消息广播被发送到系统的所有窗口。一个线程用没有超时的等待函数也许会引起系统死锁。间接创建窗口的两个例子是DDE和COM CoInitialize。因此,如果用户有一个创建窗口的线程,用MsgWaitForMultipleObjects或MsgWaitForMultipleObjectsEx函数,而不要用SignalObjectAndWait函数。

7. WaitForMultipleObjects 函数功能:

WaitForMultipleObjects函数当下列条件之一满足时返回:(1)任意一个或全部指定对象处于信号态;(2)超时间隔以过。

函数原型:

DWORD WaitForMultipleObjects(DWORD nCount,CONST HANDLE *lpHandles, BOOL fWaitAll,DWORD dwMilliseconds); 参数:

·nCount:指定由lpHandles所指向的数组中的句柄对象数目MAXIMUM-WAIT-OBJECTS。

·lpHandles:指向对象句柄数组的指针。该数组可以包含不同类型对象的句柄。在Windows NT中,句柄必须有SYNCHRONIZE访问权限。若想获得更多的信息,请查看Standard Access Rights。

·fWaitall:指定等待类型。如果为TRUE,当lpHandles指向的数组里的全部对象为信号态时,函数返回。如果为FALSE,当由lpHandles指向的数组里的任一对象为信号态时,函数返回。对于后者,返回值指出引起函数返回的对象。

·dwMilliseconds:指定以毫秒为单位的超时间隔。如果超时,即使bWaitAll参数指定的条件没有满足,函数也返回。如果dwMilliseconds是0,函数测试对象的状态并立即返回;如果dwMilliseconds是INFINITE,函数从不超时。

返回值:

如果函数调用成功,返回值表明引起函数返回的事件。可能值如下:

·WAIT-OBJECT-0到WAIT-OBJECT-0+nCount-1:如果bWaitAll为TRUE,那么返回值表明所有指定对象的状态为信号态。如果bWaitAll为FALSE,那么返回值减去WAIT-OBJECT-0表明引起函数返回的对象的pHandles数组索引。如果多于一个对象变为信号态,则返回的是数组索引最小的信号态对象索引。

·WAIT-ABANDONED-0到·WAIT-ABANDONED-0+ nCount-1:如果bWaitAll为TRUE,

那么返回值表明所有指定对象的状态为信号态,并且至少一个对象是已放弃的互斥对象。如果bWaitAll为FALSE,那么返回值减去WAIT-OBJECT-0表明引起函数返回的放弃互斥对象的pHandles数组索引。

·WAIT-TIMEOUT:超时并且由参数bWaitAll指定的条件没有满足。

如果函数调用失败,返回值是WAIT-FAILED。若想获得更多错误信息,请调用GetLastError函数。

8.CreateSemapore 函数功能:

该函数是创建一个有名或者无名信号对象。 函数原型:

HANDLE CreateSwmaphore(LPSECURITY-ATTRIBUTES lpAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName); 参数:

·lpAttributes:安全属性。如果是NULL就表示要使用默认属性。 ·lInitialCount:Semapore的初值。必须大于或等于0,并且小于或等于MaximumCount。 ·lMaximumCount:Semapore的最大值。这也就是在同一时间内能够锁住Semapore之线程的最多个数。

·lpName:Semapore的名称(一个字符串)。任何线程(或进程)都可以根据这一名称引用到这个Semaphore。这个值可以是NULL,意思是产生一个没有名字的Semaphore。

返回值:

如果成功就传回一个handle,否则传回NULL。不论哪一种情况,GetLastError都会传回一个合理的结果。如果指定的Semaphore名称已经存在,则该函数还是成功的,GetLastError会传回ERROR_ALREADY_EXISTS。

9.ReleaseSemaphore 函数功能:

该函数将指定信号对象的计数增加一个指定的数量。 函数原型:

BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount); 参数:

·hSemaphore:Semaphore的handle。

·lReleaseCount:Semaphore现值的增额。该值不可以是负值或0。 ·lpPreviousCount:借此返回Semaphore原来的值。 返回值:

如果成功,则返回TRUE。否则返回FALSE。失败时可调用GetLastError获得原因。 备注:

无论ReleaseSemaphore对于Semaphore所造成的当前值怎样增加,都绝对不会超过CreateSemaphore时所指定的ImaximumCount。

请记住,lpPreviousCount所传回来的是一个瞬间值。你不可以把lReleaseCount加上* lpPreviousCount,就当做是Semaphore的当前值,因为其他线程可能已经改变了Semaphore的值。

与mutex不同的是,调用ReleaseSemaphore的那个线程,并不一定就是调用WaitXxx 的那个线程。任何线程都可以在任何时候调用ReleaseSemaphore,解除被任何线程锁定的Semaphore。

10.InitializeCriticalSection 函数功能:

该函数初始化临界区对象。 函数原型:

VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 参数:

·lpCriticalSection:指向临界区对象的指针。 备注:

单进程的所有线程可以使用互斥同步机制的临界区对象。但是,不能保证线程获得临界区所有权的顺序,系统将对所有线程公平处理。

进程负责分配临界区对象使用的存储空间,这可以通过声明CRITICAL_SECTION类型的变量来完成。在使用临界区之前,该进程的一些线程必须使用InitializeCriticalSection或InitializeCriticalSectionAndSectiom函数来初始化该临界区对象。

11.EnterCriticalSection

函数功能:该函数是等待指定临界区对象的所有权。当调用线程被赋予所有权时,该函数返回。

函数原型:

VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 参数:

·lpCriticalSection:指向临界区对象的指针。 12.LeaveCriticalSection

函数功能:该函数释放指定临界区对象的所有权。 函数原型:

VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 参数:

·lpCriticalSection:指向临界区对象的指针。 4.1.5参考源代码

下面的程序已经在Windows 2000/XP上实现。用VC6.0创建源文件,将输入文件命名为thread.dat并放在与源文件相同的文件夹内,编译运行即可(本节中的参考源代码仅供参考)。

#include \"windows.h\" #include #include #include #include #include #include

#define READER 'R' // 读者 #define WRITER 'W' // 写者

#define INTE_PER_SEC 1000 // 每秒时钟中断数目。 #define MAX_THREAD_NUM 64 // 最大线程数目 #define MAX_FILE_NUM 32 // 最大数据文件数目 #define MAX_STR_LEN 32 // 字符串长度

int readcount=0; // 读者数目 int writecount=0; // 写者数目

CRITICAL_SECTION RP_Write; //临界区 CRITICAL_SECTION cs_Write; CRITICAL_SECTION cs_Read; struct ThreadInfo {

int serial; // 线程序号

char entity; //线程类别(判断读者线程还是写者线程) double delay; double persist; };

/////////////////////////////////////////////////////////////////////////// // 读者优先--读者线程 //p:读者线程信息

void RP_ReaderThread(void* p) {

//互斥变量

HANDLE h_Mutex;

h_Mutex=OpenMutex(MUTEX_ALL_ACCESS,FALSE,\"mutex_for_readcount\");

DWORD wait_for_mutex; //等待互斥变量所有权 DWORD m_delay; // 延迟时间

DWORD m_persist; // 读文件持续时间 int m_serial; //线程序号 //从参数中获得信息

m_serial=((ThreadInfo*)(p))->serial;

m_delay=(DWORD)(((ThreadInfo*)(p))->delay*INTE_PER_SEC); m_persist=(DWORD)(((ThreadInfo*)(p))->persist*INTE_PER_SEC); Sleep(m_delay); //延迟等待

printf(\"Reader thread %d sents the reading require.\\n\

// 等待互斥信号,保证对readcount的访问、修改互斥 wait_for_mutex=WaitForSingleObject(h_Mutex,-1); //读者数目增加 readcount++; if(readcount==1)

{

//第一个读者,等待资源

EnterCriticalSection(&RP_Write); }

ReleaseMutex(h_Mutex); //释放互斥信号

//读文件

printf(\"Reader thread %d begins to read file.\\n\Sleep(m_persist);

// 退出线程

printf(\"Reader thread %d finished reading file.\\n\//等待互斥信号,保证对readcount的访问、修改互斥 wait_for_mutex=WaitForSingleObject(h_Mutex,-1); //读者数目减少 readcount--; if(readcount==0) {

//如果所有读者读完,唤醒写者 LeaveCriticalSection(&RP_Write); }

ReleaseMutex(h_Mutex); //释放互斥信号 }

/////////////////////////////////////////////////////////////////////////// // 读者优先--写者线程 //p:写者线程信息

void RP_WriterThread(void* p) {

DWORD m_delay; // 延迟时间

DWORD m_persist; // 写文件持续时间 int m_serial; //线程序号 //从参数中获得信息

m_serial=((ThreadInfo*)(p))->serial;

m_delay=(DWORD)(((ThreadInfo*)(p))->delay*INTE_PER_SEC); m_persist=(DWORD)(((ThreadInfo*)(p)) ->persist*INTE_PER_SEC); Sleep(m_delay); //延迟等待

printf(\"Writer thread %d sents the writing require.\\n\// 等待资源

EnterCriticalSection(&RP_Write);

//写文件

printf(\"Writer thread %d begins to Write to the file.\\n\Sleep(m_persist);

// 退出线程

printf(\"Writer thread %d finished Writing to the file.\\n\//释放资源

LeaveCriticalSection(&RP_Write); }

/////////////////////////////////////////////////////////////////////////// // 读者优先处理函数 //file:文件名

void ReaderPriority(char* file) {

DWORD n_thread=0; //线程数目 DWORD thread_ID; //线程ID

DWORD wait_for_all; //等待所有线程结束 //互斥对象

HANDLE h_Mutex;

h_Mutex=CreateMutex(NULL,FALSE,\"mutex_for_readcount\");

//线程对象的数组

HANDLE h_Thread[MAX_THREAD_NUM]; ThreadInfo thread_info[MAX_THREAD_NUM];

readcount=0; // 初始化 readcount

InitializeCriticalSection(&RP_Write); //初始化临界区 ifstream inFile;

inFile.open(file); //打开文件 printf(\"Reader Priority:\\n\\n\"); while(inFile) {

//读入每一个读者、写者的信息 inFile>>thread_info[n_thread].serial; inFile>>thread_info[n_thread].entity; inFile>>thread_info[n_thread].delay; inFile>>thread_info[n_thread++].persist; inFile.get( ); }

for(int i=0;i< (int)(n_thread);i++) {

if(thread_info[i].entity==READER || thread_info[i].entity=='R')

{

h_Thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(RP_ReaderThread),&thread_info[i],0,&thread_ID); //创建读者线程 } else{

//创建写者线程

h_Thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(RP_WriterThread),&thread_info[i],0,&thread_ID); } }

//等待所有线程结束

wait_for_all=WaitForMultipleObjects(n_thread,h_Thread,TRUE,-1); printf(\"All reader and writer have finished operating.\\n\"); }

/////////////////////////////////////////////////////////////////////////// // 写者优先--读者线程 //p:读者线程信息

void WP_ReaderThread(void* p) {

//互斥变量

HANDLE h_Mutex1;

h_Mutex1=OpenMutex(MUTEX_ALL_ACCESS,FALSE,\"mutex1\"); HANDLE h_Mutex2;

h_Mutex2=OpenMutex(MUTEX_ALL_ACCESS,FALSE,\"mutex2\");

DWORD wait_for_mutex1; //等待互斥变量所有权 DWORD wait_for_mutex2;

DWORD m_delay; // 延迟时间

DWORD m_persist; // 读文件持续时间 int m_serial; //线程序号 //从参数中获得信息

m_serial=((ThreadInfo*)(p))->serial;

m_delay=(DWORD)(((ThreadInfo*)(p))->delay*INTE_PER_SEC); m_persist=(DWORD)(((ThreadInfo*)(p)) ->persist*INTE_PER_SEC); Sleep(m_delay); //延迟等待

printf(\"Reader thread %d sents the reading require.\\n\wait_for_mutex1= WaitForSingleObject(h_Mutex1,-1); //进入读者临界区

EnterCriticalSection(&cs_Read);

// 阻塞互斥对象mutex2,保证对readcount的访问、修改互斥 wait_for_mutex2= WaitForSingleObject(h_Mutex2,-1); //修改读者数目 readcount++; if(readcount==1) {

//如果是第一个读者,等待写者写完 EnterCriticalSection(&cs_Write); }

ReleaseMutex(h_Mutex2); //释放互斥信号mutex2 // 让其他读者进入临界区

LeaveCriticalSection(&cs_Write); ReleaseMutex(h_Mutex1); //读文件

printf(\"Reader thread %d begins to read file.\\n\Sleep(m_persist); // 退出线程

printf(\"Reader thread %d finished reading file.\\n\

// 阻塞互斥对象mutex2,保证对readcount的访问、修改互斥 wait_for_mutex2= WaitForSingleObject(h_Mutex2,-1); readcount--; if(readcount==0) {

// 最后一个读者,唤醒写者 LeaveCriticalSection(&cs_Write); }

ReleaseMutex(h_Mutex2); //释放互斥信号 }

/////////////////////////////////////////////////////////////////////////// // 写者优先--写者线程 //p:写者线程信息

void WP_WriterThread(void* p) {

DWORD m_delay; // 延迟时间

DWORD m_persist; // 写文件持续时间 int m_serial; //线程序号 DWORD wait_for_mutex3; //互斥对象

HANDLE h_Mutex3;

h_Mutex3= OpenMutex(MUTEX_ALL_ACCESS,FALSE,\"mutex3\");

//从参数中获得信息

m_serial=((ThreadInfo*)(p))->serial;

m_delay=(DWORD)(((ThreadInfo*)(p))->delay*INTE_PER_SEC); m_persist=(DWORD)(((ThreadInfo*)(p))->persist*INTE_PER_SEC); Sleep(m_delay); //延迟等待 printf(\"Writer thread %d sents the writing require.\\n\

// 阻塞互斥对象mutex3,保证对writecount的访问、修改互斥 wait_for_mutex3= WaitForSingleObject(h_Mutex3,-1);

writecount++; //修改读者数目 if(writecount==1) {

//第一个写者,等待读者读完 EnterCriticalSection(&cs_Read); }

ReleaseMutex(h_Mutex3);

//进入写者临界区

EnterCriticalSection(&cs_Write); //写文件

printf(\"Writer thread %d begins to Write to the file.\\n\Sleep(m_persist);

// 退出线程

printf(\"Writer thread %d finishing Writing to the file.\\n\

//离开临界区

LeaveCriticalSection(&cs_Write);

// 阻塞互斥对象mutex3,保证对writecount的访问、修改互斥 wait_for_mutex3= WaitForSingleObject(h_Mutex3,-1);

writecount--; //修改读者数目 if(writecount==0) {

//写者写完,读者可以读

LeaveCriticalSection(&cs_Read); }

ReleaseMutex(h_Mutex3); }

/////////////////////////////////////////////////////////////////////////// // 写者优先处理函数 //file:文件名

void WriterPriority(char* file) {

DWORD n_thread=0; //线程数目 DWORD thread_ID; //线程ID

DWORD wait_for_all; //等待所有线程结束

//互斥对象

HANDLE h_Mutex1;

h_Mutex1=CreateMutex(NULL,FALSE,\"mutex1\"); HANDLE h_Mutex2;

h_Mutex2=CreateMutex(NULL,FALSE,\"mutex2\"); HANDLE h_Mutex3;

h_Mutex3=CreateMutex(NULL,FALSE,\"mutex3\");

//线程对象

HANDLE h_Thread[MAX_THREAD_NUM]; ThreadInfo thread_info[MAX_THREAD_NUM];

readcount=0; // 初始化 readcount writecount=0; // 初始化writecount InitializeCriticalSection(&cs_Write); //初始化临界区 InitializeCriticalSection(&cs_Read); ifstream inFile;

inFile.open(file); //打开文件 printf(\"Writer Priority:\\n\\n\"); while(inFile) {

//读入每一个读者、写者的信息 inFile>>thread_info[n_thread].serial; inFile>>thread_info[n_thread].entity; inFile>>thread_info[n_thread].delay; inFile>>thread_info[n_thread++].persist; inFile.get( ); }

for(int i=0;i< (int)(n_thread);i++) {

if (thread_info[i].entity==READER || thread_info[i].entity=='R') {

//创建读者线程

h_Thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(RP_WriterThread),&thread_info[i],0,&thread_ID); } else{

//创建写者线程

h_Thread[i]=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)(WP_WriterThread),&thr

ead_info[i],0,&thread_ID); } }

//等待所有线程结束

wait_for_all=WaitForMultipleObjects(n_thread,h_Thread,TRUE,-1); printf(\"All reader and writer have finished operating.\\n\"); }

/////////////////////////////////////////////////////////////////////////////// //主函数

int main(int argc,char* argv[]) {

char ch;

while (true) {

//打印提示信息

printf(\"************************************************\\n\"); printf(\" 1:Reader Priority\\n\"); printf(\" 2:Writer Priority\\n\"); printf(\" 3:Exit Priority\\n\");

printf(\"************************************************\\n\"); printf(\"Enter your choice(1,2 or 3): \"); //如果输入信息不正确,继续输入 do{

ch=(char)_getch( );

}while(ch != '1' &&ch != '2' && ch != '3');

system(\"cls\"); //选择3,返回 if(ch=='3')

return 0; //选择1,读者优先 else if(ch=='1')

ReaderPriority(\"thread.dat\"); //选择2,写者优先 else if(ch=='2')

WriterPriority(\"thread.dat\"); //结束

printf(\"\\nPress Any Key To Continue: \"); _getch( ); system(\"cls\"); }

return 0;

}

说明:

在Win32 API中,互斥对象Mutex与P、V中的互斥信号量有类似的地方,但也有不同:在P、V操作中的互斥信号量可以有一个任意大小的初值,但互斥对象Mutex没有,它可以被看成是初值为1的互斥信号量。而且一个线程在取得Mutex的所有权之后,即使不调用ReleaseMutex函数,在线程结束时,线程也会自动释放Mutex的所有权。

临界区对象CriticalSection则与P、V操作中初值为1的互斥信号量语意相同。它在线程结束时会将CriticalSection的所有权传递给它的同类型线程。这样就可以满足在一个线程中申请所有权,在另一个线程释放所有权的要求。在读者优先中的write互斥信号量以及写者优先中的read和write互斥信号量就应该用CriticalSection实现而不应该用Mutex。

用WaitForSingleSignal函数可以获得一个Mutex的所有权,类似于P操作,而ReleaseMutex函数可以释放一个Mutex的所有权,类似于V操作。

用EnterCriticalSection函数可以进入一个CriticalSection,类似于P操作,而LeaveCriticalSection函数离开一个CriticalSection,类似于V操作。

备注:

ReaderPriority和WritePriority函数最后都有

wait_for_all=WaitForMultipleObjects(n_thread,h_Thread,TRUE,-1); 是因为主函数要等待所有的线程都结束之后才退出。因为不知道有多少线程,所以源文件最初有:

#define MAX_THREAD_NUM 64 //最大线程数目

即线程最多不能超过MAX_THREAD_NUM个。线程对象的数组大小为MAX_THREAD_NUM。如果创建的线程没有那么多,空间会有浪费,但是可以达到牺牲空间来节省时间的目的。

有的书上还有其他的处理方法。一种是在主函数的最后加上Sleep(1000),即通过主函数睡眠的方法等待其他线程结束,这当然不是一种很好的方法,因为睡眠等待的时间没法控制。另一种方法是增加循环变量threadCount(线程的个数),每个线程结束的就会执行语句

threadCount--; 主函数的最后测试:

while(threadcount>0);

但是这种方式会让主函数循环等待,浪费了CPU资源。

相比之下,考虑到运行效率,还是实例中给出的方法比较好写些。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- azee.cn 版权所有

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务