GetModuleFileName(NULL, szFilename, MAX_PATH) ;
// 格式化用于子进程的命令行,字符串“child”将作为形参传递给子进程的main函数 TCHAR szCmdLine[MAX_PATH] ;
//实验2-2步骤3:将下句中的字符串child改为别的字符串,重新编译执行,执行前请先保存已经完成的工作
sprintf(szCmdLine, \
// 子进程的启动信息结构 STARTUPINFO si;
ZeroMemory(&si,sizeof(si)) ; si.cb = sizeof(si) ; // 应当是此结构的大小
// 返回的用于子进程的进程信息 PROCESS_INFORMATION pi;
// 用同样的可执行文件名和命令行创建进程,并指明它是一个子进程 BOOL bCreateOK=CreateProcess( szFilename, // 产生的应用程序的名称 (本EXE文件) szCmdLine, // 告诉我们这是一个子进程的标志 NULL, // 用于进程的缺省的安全性 NULL, // 用于线程的缺省安全性 FALSE, // 不继承句柄 CREATE_NEW_CONSOLE, //创建新窗口 NULL, // 新环境 NULL, // 当前目录 &si, // 启动信息结构 &pi ) ; // 返回的进程信息
// 释放指向子进程的引用 if (bCreateOK) {
CloseHandle(pi.hProcess) ; CloseHandle(pi.hThread) ; } }
void Parent() {
// 创建“自杀”互斥程序体
HANDLE hMutexSuicide=CreateMutex( NULL, // 缺省的安全性 TRUE, // 最初拥有的 g_szMutexName) ; // 互斥体名称 if (hMutexSuicide != NULL) {
// 创建子进程
std :: cout << \ StartClone() ;
// 指令子进程“杀”掉自身
std :: cout << \ //等待父进程的键盘响应
16
getchar() ; //释放互斥体的所有权,这个信号会发送给子进程的WaitForSingleObject过程 ReleaseMutex(hMutexSuicide) ;
// 消除句柄
CloseHandle(hMutexSuicide) ; } }
void Child() {
// 打开“自杀”互斥体
HANDLE hMutexSuicide = OpenMutex( SYNCHRONIZE, // 打开用于同步 FALSE, // 不需要向下传递 g_szMutexName) ; // 名称 if (hMutexSuicide != NULL) {
// 报告我们正在等待指令
std :: cout <<\ //子进程进入阻塞状态,等待父进程通过互斥体发来的信号 WaitForSingleObject(hMutexSuicide, INFINITE) ;
//实验2-2步骤5:将上句改为WaitForSingleObject(hMutexSuicide, 0) ,重新编译执行
// 准备好终止,清除句柄
std :: cout << \ CloseHandle(hMutexSuicide) ; } }
int main(int argc, char* argv[] ) {
// 决定其行为是父进程还是子进程
if (argc>1 && :: strcmp(argv[1] , \ {
Child() ; } else {
Parent() ; }
return 0;}
17
实验三 进程同步的经典算法
背景知识
Windows 2000提供的常用对象可分成三类:核心应用服务、线程同步和线程间通讯。其中,开发人员可以使用线程同步对象来协调线程和进程的工作,以使其共享信息并执行任务。此类对象包括互锁数据、临界段、事件、互斥体和信号等。
多线程编程中关键的一步是保护所有的共享资源,工具主要有互锁函数、临界段和互斥体等;另一个实质性部分是协调线程使其完成应用程序的任务,为此,可利用内核中的事件对象和信号。
在进程内或进程间实现线程同步的最方便的方法是使用事件对象,这一组内核对象允许一个线程对其受信状态进行直接控制 (见表3-1) 。
而互斥体则是另一个可命名且安全的内核对象,其主要目的是引导对共享资源的访问。拥有单一访问资源的线程创建互斥体,所有想要访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时立即释放互斥体,以允许下一个等待线程获得互斥体,然后接着进行下去。
与事件对象类似,互斥体容易创建、打开、使用并清除。利用CreateMutex() API可创建互斥体,创建时还可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线程释放互斥体。
表3-1 用于管理事件对象的API API名称 CreateEvent() OpenEvent() SetEvent() ResetEvent() PulseEvent() 描述 在内核中创建一个新的事件对象。此函数允许有安全性设置、手工还是自动重置的标志以及初始时已接受还是未接受信号状态的标志 创建对已经存在的事件对象的引用。此API函数需要名称、继承标志和所需的访问级别 将手工重置事件转化为已接受信号状态 将手工重置事件转化为非接受信号状态 将自动重置事件对象转化为已接受信号状态。当系统释放所有的等待它的线程时此种转化立即发生
为了获得互斥体,首先,想要访问调用的线程可使用OpenMutex() API来获得指向对象的句柄;然后,线程将这个句柄提供给一个等待函数。当内核将互斥体对象发送给等待线程时,就表明该线程获得了互斥体的拥有权。当线程获得拥有权时,线程控制了对共享资源的访问——必须设法尽快地放弃互斥体。放弃共享资源时需要在该对象上调用ReleaseMute() API。然后系统负责将互斥体拥有权传递给下一个等待着的线程 (由到达时间决定顺序) 。
18
1、实验目的
1) 回顾系统进程、线程的有关概念,加深对Windows 2000线程的理解。
2) 了解互斥体对象,通过对生产者消费者等进程间同步与互斥经典算法的实现,加深对P、V原语以及利用P、V原语进行进程间同步与互斥操作的理解。
2、实验内容和步骤
(1). 生产者消费者问题
步骤1:创建一个“Win32 Consol Application”工程,然后拷贝清单3-1中的程序,编译成可执行文件。
步骤2:在“命令提示符”窗口运行步骤1中生成的可执行文件。运行结果: 范例:E:\\课程\\os课\\os实验\\程序\\os11\\debug>os31 (假设编译生成的可执行文件是os31.exe)
___
步骤3:仔细阅读源程序,找出创建线程的WINDOWS API函数,回答下列问题:线程的第一个执行函数是什么(从哪里开始执行)?它位于创建线程的API函数的第几个参数中? __答:第一个执行函数是Producer;位于第三个参数中。
步骤4:修改清单3-1中的程序,调整生产者线程和消费者线程的个数,使得消费者数目大与生产者,看看结果有何不同。运行结果:
_
从中你可以得出什么结论:
19
一直等待,生产者小于消费者,消费者处于等待状态
步骤5:修改清单3-1中的程序,按程序注释中的说明修改信号量EmptySemaphore的初始化方法,看看结果有何不同。运行结果:
_
步骤6:根据步骤4的结果,并查看MSDN,回答下列问题 1)CreateMutex中有几个参数,各代表什么含义。
三个,
1. LPSECURITY_ATTRIBUTES lpMutexAttributes 代表安全属性的指针 2. BOOL bInitialOwner代表布尔bInitialOwner 3.LPCTSTR lpName 代表LPCTSTR类型lpName
2)CreateSemaphore中有几个参数,各代表什么含义,信号量的初值在第几个参数中。 四个
1、表示采用不允许继承的默认描述符2、设置信号机的初始计数3、设置信号机的最大计数 4、指定信号机对象的名称。
3)程序中P、V原语所对应的实际Windows API函数是什么,写出这几条语句。 _____void Produce() {
std::cout << std::endl<< \ std::cout << \}
//把新生产的产品放入缓冲区 void Append() {
std::cerr << \ buffer[in] = ProductID; in = (in+1)%SIZE_OF_BUFFER;
std::cerr << \
//输出缓冲区当前的状态
20