当前位置:首页 > 《计算机操作系统》实验指导书
std :: cout << “Child quiting. ” << std :: endl; :: CloseHandle(hMutexSuicide) ; } }
int main(int arqc, char* argv[] ) {
// 决定其行为是父进程还是子进程
if (argc > l && :: strcmp(argv[l] , “child” ) = = 0) {
Child() ; } else {
Parent() ; }
return 0; }
程序说明了一个进程从“生”到“死”的整个一生。第一次执行时,它创建一个子进程,其行为如同“父亲”。在创建子进程之前,先创建一个互斥的内核对象,其行为对于子进程来说,如同一个“自杀弹”。当创建子进程时,就打开了互斥体并在其他线程中进行别的处理工作,同时等待着父进程使用ReleaseMutex() API发出“死亡”信号。然后用Sleep() API调用来模拟父进程处理其他工作,等完成时,指令子进程终止。
当调用ExitProcess() 时要小心,进程中的所有线程都被立刻通知停止。在设计应用程序时,必须让主线程在正常的C++ 运行期关闭 (这是由编译器提供的缺省行为) 之后来调用这一函数。当它转向受信状态时,通常可创建一个每个活动线程都可等待和停止的终止事件。
在正常的终止操作中,进程的每个工作线程都要终止,由主线程调用ExitProcess()。接着,管理层对进程增加的所有对象释放引用,并将用 GetExitCodeProcess() 建立的退出代码从STILL_ACTIVE改变为在ExitProcess() 调用中返回的值。最后,主线程对象也如同进程对象一样转变为受信状态。
等到所有打开的句柄都关闭之后,管理层的对象管理器才销毁进程对象本身。还没有一种函数可取得终止后的进程对象为其参数,从而使其“复活”。当进程对象引用一个终止了的对象时,有好几个API函数仍然是有用的。进程可使用退出代码将终止方式通知给调用GetExitCodeProcess() 的其他进程。同时,GetProcessTimes() API函数可向主调者显示进程的终止时间。
运行结果:
1) __________________________________________________________________ 表示:______________________________________________________________ 2) __________________________________________________________________ 表示:______________________________________________________________
在熟悉源代码的基础上,利用本实验介绍的API函数来尝试改进本程序 (例如使用GetProcessTimes() API函数) 并运行。请描述你所做的工作:
____________________________________________________________________ ________________________________________________________________________ ________________________________________________________________________ ________________________________________________________________________
四、实验总结
请总结一下本次实验的收获、教训和感受,结合课本内容谈一下你对进程的理解。
实验二 并发与调度
一、实验目的
在本实验中,通过对事件和互斥体对象的了解,来加深对Windows 2000线程同步的理解。通过分析实验程序,了解管理事件对象的API。了解在进程中如何使用事件对象,在进程中如何使用互斥体对象,线程如何通过文件映射对象发送数据。
二、实验环境
硬件环境:计算机一台,局域网环境;
软件环境:Windows 2000 Professional,Visual C++ 6.0专业版或企业版。 三、实验内容和步骤 第一部分:互斥体对象
本程序中显示的类CCountUpDown使用了一个互斥体来保证对两个线程间单一数值的访问。每个线程都企图获得控制权来改变该数值,然后将该数值写入输出流中。创建者实际上创建的是互斥体对象,计数方法执行等待并释放,为的是共同使用互斥体所需的资源 (因而也就是共享资源) 。
1、利用互斥体保护共享资源
// mutex项目
# include
// 利用互斥体来保护同时访问的共享资源 class CCountUpDown {
public:
// 创建者创建两个线程来访问共享值 CCountUpDown(int nAccesses) :
m_hThreadlnc(INVALID_HANDLE_VALUE) , m_hThreadDec(INVALID_HANDLE_VALUE) , m_hMutexValue(INVALID_HANDLE_VALUE) , m_nValue(0) ,
m_nAccess(nAccesses) {
// 创建互斥体用于访问数值
m_hMutexValue = :: CreateMutex( NULL, // 缺省的安全性 TRUE, // 初始时拥有,在所有的初始化结束时将释放 NULL) ; // 匿名的 m_hThreadInc = :: CreateThread( NULL, // 缺省的安全性 0, // 缺省堆栈 IncThreadProc, // 类线程进程 reinterpret_cast
// 允许另一线程获得互斥体
:: ReleaseMutex(m_hMutexValue) ; }
// 解除程序释放对对象的引用
virtual ~CCountUpDown()
{
:: CloseHandle(m_hThreadInc) ; :: CloseHandle(m_hThreadDec) ; :: CloseHandle(m_hMutexValue) ; }
// 简单的等待方法,在两个线程终止之前可暂停主调者 virtual void WaitForCompletion() {
// 确保所有对象都已准备好
if (m_hThreadInc != INVALID_HANDLE_VALUE && m_hThreadDec != INVALID_HANDLE_VALUE) {
// 等待两者完成 (顺序并不重要)
:: WaitForSingleObject(m_hThreadInc, INFINITE) ; :: WaitForSingleObject(m_hThreadDec, INFINITE) ; } }
protected:
// 改变共享资源的简单的方法 virtual void DoCount(int nStep) {
// 循环,直到所有的访问都结束为止 while (m_nAccess > 0) {
// 等待访问数值
:: WaitForSingleObject(m_hMutexValue, INFINITE) ; // 改变并显示该值 m_nValue += nStep;
std :: cout << “thread: ” << :: GetCurrentThreadId() << “value: ” << m_nvalue
<< “access: ” << m_nAccess << std :: endl; // 发出访问信号并允许线程切换 --m_nAccess; :: sleep(l000) ; // 使显示速度放慢 // 释放对数值的访问
:: ReleaseMutex(m_hMutexValue) ; } }
static DWORD WINAPI IncThreadProc(LPVOID lpParam) {
// 将参数解释为 ?this? 指针 CCountUpDown* pThis =
reinterpret_cast < CCountUpDown* > (lpParam) ;
// 调用对象的增加方法并返回一个值
pThis -> DoCount(+1) ; return(0) ; }
static DWORD WINAPI DecThreadProc(LPVOID lpParam) {
// 将参数解释为 ?this? 指针 CCountUpDown* pThis =
reinterpret_cast
protected:
HANDLE m_hThreadInc; HANDLE m_hThreadDec; HANDLE m_hMutexValue;
int m_nValue; int m_nAccess ;
} ;
void main() {
CCountUpDown ud(50) ; ud.WaitForCompletion() ; }
分析程序的运行结果,可以看到线程 (加和减线程) 的交替执行 (因为Sleep() API允许Windows切换线程) 。在每次运行之后,数值应该返回初始值 (0) ,因为在每次运行之后写入线程在等待队列中变成最后一个,内核保证它在其他线程工作时不会再运行。
1) 请描述运行结果 (如果运行不成功,则可能的原因是什么?) :
____________________________________________________________________ ________________________________________________________________________ 2) 根据运行输出结果,对照分析程序,可以看出程序运行的流程吗?请简单描述: ____________________________________________________________________ ______________________________________________________________________
第二部分线程通过文件对象发送数据
Windows 2000提供的线程间通讯类内核对象允许同一进程或跨进程的线程之间互相发送信息,包括文件、文件映射、邮件位和命名管道等,其中最常用的是文件和文件映射。这类对象允许一个线程很容易地向同一进程或其他进程中的另一线程发送信息。 1、演示线程通过文件对象发送数据
# inc1ude
static LPCTSTR g_szFileName = “w2kdg.Fileobj.file.data.txt” ;
// 在数据文件中读取当前数据的简单线程时将传递来的该数据增加,并写回数据文件中 static DWORD WINAPI ThreadProc (LPVOID lpParam) {
// 将参数翻译为长整数
LONG nAdd = reinterpret_cast
:: GetTempPath(MAX_PATH, szFullName) ; // 取得路径 :: strcat(szFullName, g_szFileName) ; // 打开文件对象
HANDLE hFile = :: CreateFile( szFullName, // 文件的完全名称 GENERIC-READ | GENERIC_WRITE, // 具有所有的访问权 FILE_SHARE_READ, // 允许其他线程读取 NULL, // 缺省的安全性 OPEN_ALWAYS, // 创建或打开文件 FILE_ATTRIBUTE_NORMAL, // 普通文件 NULL) ; // 无模板文件 if (hFile != INVALID_HANDLE_VALUE) {
// 读取当前数据 LONG nValue(0) ; DWORD dwXfer(0) ; :: ReadFile( hFile, // 要读取的文件 reinterpret_cast
if (dwXfer == sizeof(nValue) ) {
std :: cout << “read: ” << nValue << std :: endl; }
// 增加数值
共分享92篇相关文档