1、实验三 进程同步实验讲解实验三 进程同步机制一、实验内容: 学习Windows有关进程/线程同步的背景知识和API,学习windows平台下常用的同步方式,并分析2个实验程序(利用信号量实现两个进程间的同步和利用互斥量实现读者写者问题),观察程序的运行情况并分析执行结果。二、实验目的: 在本实验中,通过对互斥量(Mutex)和信号量(Semaphore)对象的了解,来加深对Windows 进程、线程同步的理解。(1) 了解互斥量和信号量对象。(2) 通过分析实验程序,理解管理信号量对象的API。(3) 理解在进程中如何使用信号量对象。(4) 通过分析实验程序,理解在线程中如何使用互斥量对象。(
2、5) 理解父进程创建子进程的程序设计方法,理解在主线程中创建子线程的方法。三、实验要求:(1) 理解Windows有关进程/线程同步的背景知识和API。(2) 按要求运行2个程序,观察程序执行的结果,并给出要求的结果分析。(3) 参照3-2程序,写出一个实现单个生产者消费者问题的算法,可以使用单个缓冲区,也可以使用缓冲池,生产者随机产生任意形式的数据并放入缓冲区中,消费者则以随机的时间间隔从缓冲区中取数据,随机时间请使用随机数产生。四、并发与同步的背景知识Windows开发人员可以使用同步对象来协调线程和进程的工作,以使其共享信息并执行任务。此类对象包括互斥量Mutex、信号量Semaphor
3、e、事件Event等。多进程、多线程编程中关键的一步是保护所有的共享资源,工具主要有互斥量Mutex和信号量Semaphore等;另一个是协调线程使其完成应用程序的任务,为此,可利用内核中的信号量对象或事件对象。互斥量是一个可命名且安全的内核对象,主要目的是引导对共享资源的访问。拥有单一访问资源的线程创建互斥体,所有希望访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时立即释放互斥体,以允许下一个等待线程获得互斥体,然后接着进行下去。利用CreateMutex() API可创建互斥量,创建时可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,
4、才允许创建线程释放互斥体。为了获得互斥体,首先,想要访问调用的线程可使用OpenMutex() API来获得指向对象的句柄;然后,线程将这个句柄提供给一个等待函数。当内核将互斥体对象发送给等待线程时,就表明该线程获得了互斥体的拥有权。当线程获得拥有权时,线程控制了对共享资源的访问必须设法尽快地放弃互斥体。放弃共享资源时需要在该对象上调用ReleaseMutex() API。然后系统负责将互斥量拥有权传递给下一个等待着的线程 (由到达时间决定顺序) 。信号量Semaphore与互斥量Mutex的用法不同,互斥量Mutex保证任意时刻只能有一个进程或线程获得互斥体,信号量允许多个线程同时使用共享资
5、源,这与操作系统中的Wait/Signal操作【也称PV操作】相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数或者0。每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理
6、完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。 此外,windows还提供了另外一个容易误导大家理解的同步对象,取名为Critical Section,中文取名为临界区,请大家注意与课本讲的临界资源、临界区的概念相区分。课本上临界区指“对临界资源进行访问的代码”;而这种称为“Critical Section”互斥机制,并不是这个意思,而是访问临界区之前的一种加锁机制,与互斥量Mutex的作用类似,只是“Critical Section”互斥机制只能在同一进程内部各个线程间使用,而Mutex互斥
7、机制是可以跨进程使用的。五、实验步骤1. 信号量Semaphore对象清单3-1程序展示如何在进程间使用信号量对象。父进程启动时,利用CreateSemaphore () API创建一个命名的、可共享的信号量对象,并利用CreateProcess()创建子进程,然后调用WaitForSingleObject()函数去获取信号量,但是由于信号量的初始值为0,所以父进程阻塞在此,无法成功占有信号量;子进程创建后,调用OpenSemaphore()打开父进程创建的信号量,然后调用ReleaseSemaphore()函数释放了1个信号量,此后,处于阻塞状态的父进程获取了子进程释放的信号量,从而解除了阻
8、塞,继续运行至程序结束。清单3-1 创建和打开信号量对象在进程间传送信号/ Semaphore信号量项目# include # include # include / 定义一个信号量的名字static LPCTSTR g_szSemaphoreName = shmtu.os2012.semaphore;/ 本方法只是创建了一个进程的副本,以子进程模式 (由命令行指定) 工作BOOL CreateChild() / 提取当前可执行文件的文件名 TCHAR szFilenameMAX_PATH ; GetModuleFileName(NULL, szFilename, MAX_PATH) ; /
9、格式化用于子进程的命令行,指明它是一个EXE文件和子进程 TCHAR szCmdLineMAX_PATH ; sprintf(szCmdLine, %s child , szFilename) ; / 子进程的启动信息结构 STARTUPINFO si; ZeroMemory(reinterpret_cast(&si), sizeof(si) ; si.cb = sizeof(si); / 必须是本结构的大小 / 返回的子进程的进程信息结构 PROCESS_INFORMATION pi; / 使用同一可执行文件和告诉它是一个子进程的命令行创建进程 BOOL bCreateOK = Create
10、Process( szFilename, / 生成的可执行文件名 szCmdLine, / 指示其行为与子进程一样的标志 NULL, / 子进程句柄的安全性 NULL, / 子线程句柄的安全性 FALSE, / 不继承句柄 CREATE_NEW_CONSOLE, / 特殊的创建标志 NULL, / 新环境 NULL, / 当前目录 &si, / 启动信息结构 &pi ) ; / 返回的进程信息结构 / 释放对子进程的引用 if (bCreateOK) CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return(bCreateOK) ;/
11、下面的方法创建一个信号量和一个子进程,然后等待子进程在返回前释放一个信号void WaitForChild() HANDLE hSemaphore = CreateSemaphore( /创建一个信号量,初始值0,最大值为1 NULL, / 缺省的安全性,子进程将具有访问权限 0, /初始值0 1, /最大值为1 g_szSemaphoreName /信号量名称 ); if (hSemaphore != NULL) cout 信号量对象已经建立啦。 endl; / 创建子进程 if ( CreateChild() cout 父进程建立了一个子进程 endl; / 等待,直到子进程发出信号 co
12、ut 因为当前信号量的值为0,所以父进程暂时阻塞在此啦. endl; WaitForSingleObject(hSemaphore, INFINITE); cout 因为子进程释放了1个信号量,所以,父进程可以解除阻塞啦。 endl; / 清除句柄 CloseHandle(hSemaphore); hSemaphore=INVALID_HANDLE_VALUE; / 以下方法在子进程模式下被调用,其功能只是释放1个信号量void SignalParent() / 尝试打开句柄 cout child process begining. endl; HANDLE hSemaphore = Open
13、Semaphore( SEMAPHORE_ALL_ACCESS, / 所要求的最小访问权限 FALSE, / 不是可继承的句柄 g_szSemaphoreName); / 信号量名称 if(hSemaphore != NULL) cout如果你同意释放一个信号量,请按任意键endl; getchar(); cout 此刻,子进程释放了1个信号量. 1 & strcmp(argv1 , child )= 0) / 向父进程发出信号 SignalParent() ; cout 子进程马上就要运行结束了。请按任意键继续。 endl ; else / 创建一个信号量并等待子进程 WaitForChil
14、d(); cout 父进程马上就要运行结束了。请按任意键继续。 endl ; getchar(); return 0;步骤1:编译并执行3-1.exe程序。程序运行结果是 (分行书写) : _ _ _ _ _ _阅读和分析程序3-1,请回答:(1) 程序中,创建一个信号量使用了哪一个系统函数?创建时设置的信号量初始值是多少,最大值是多少?a. _b. _(2) 创建一个进程 (子进程) 使用了哪一个系统函数?_(3) 从步骤1的输出结果,对照分析3-1程序,能够看出程序运行的流程吗?请简单描述:_步骤2:编译程序生成执行文件3-1.exe,在命令行状态下执行程序,分别使用格式:(1) 3-1
15、child(2) 3-1 或3-1 *运行程序,记录执行的结果,并分行说明产生不同结果的原因。2. 互斥量Mutex对象注意:多线程编程需要在Visual C+中设定多线程C运行时库。打开你的工程项目后,在Visual C+的“Project”菜单下面,选择“Settings”菜单,如下两图所示,可以分别设定Debug版本和Release版本所使用的多线程C运行时库,以Debug开头的都是为Debug版本准备的,都选Multithread版本。如果使用标准 C 库而调用VC运行时库函数,则在程序的link阶段会提示如下错误:error LNK2001: unresolved external
16、symbol _endthreadexerror LNK2001: unresolved external symbol _beginthreadex清单3-2的程序中是读者写者问题的一个实现,满足读者优先原则,使用同步机制的互斥量实现,对每个读者和写者分别用一个线程来表示。测试数据文件的数据格式说明:测试数据文件包括n行测试数据,每行描述创建的是用于产生读者还是写者的数据。每行测试数据包括4个字段,各字段间用空格分隔。 第一字段为线程序号。 第二字段表示相应线程角色,W表示写者,R表示读者。 第三字段为线程延迟。 第四字段为线程读写操作持续时间。清单3-2 利用互斥量Mutex实现读者写者问
17、题#include windows.h#include process.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 / 字符串长度volatile int readcount = 0; / 读者数
18、目HANDLE RP_Write;struct ThreadInfo / 定义线程数据结构int serial; / 线程序号char entity; / 线程类别(判断是读者线程还是写者线程)double delay; / 线程延迟double persist; / 线程读写操作持续时间;/ 读者优先读者线程/ p:读者线程信息unsigned int _stdcall RP_ReaderThread(void *p) / 互斥变量 HANDLE h_Mutex; h_Mutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE,mutex_for_readcount);
19、 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
20、 thread %d sents the reading require.n,m_serial); /等待互斥信号,保证对readcount的访问、修改互斥 wait_for_mutex = WaitForSingleObject(h_Mutex,-1); / 读者数目增加 readcount+; if(readcount =1) /这是第一个读者,第二个读者到来时,readcount为2,if的条件不满足,不会进入if语句内部执行 /这是第一个读者,如果此刻没有写者正在写,则RP_Write信号量状态为可用(未占用),那它就必须先占用(锁定)RP_Write信号量,这就实现了读-写互斥 /如
21、果此刻有写者正在写,则RP_Write信号量被写者占用(锁定),读者想对RP_Write加锁就会阻塞在此,等待RP_Write信号量释放后,才能继续运行 WaitForSingleObject(RP_Write, INFINITE); ReleaseMutex(h_Mutex) ; /释放互斥信号 / 读文件 printf(Reader thread %d begins to read file.n,m_serial); Sleep(m_persist) ; / 退出线程 printf(Reader thread %d finished reading file.n,m_serial); /等
22、待互斥信号,保证对readcount的访问、修改互斥 wait_for_mutex = WaitForSingleObject(h_Mutex,-1) ; /读者数目减少 readcount- ; if(readcount = 0) /如果所有读者读完,则释放RP_Write信号量;此刻若有写者正在等待(处于阻塞状态),将(自动)唤醒写者 ReleaseMutex(RP_Write); ReleaseMutex(h_Mutex) ; /释放互斥信号 return 0;/ 读者优先写者线程/ p:写者线程信息unsigned int _stdcall RP_WriterThread(void*
23、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
24、require.n,m_serial); / 写者在进行写数据之前,必须确定没有其它写者正在写,也没有其它读者在读, / 通过RP_Write互斥量实现,若有其它写者正在写或其它读者在读,则该写者调用“WaitForSingleObject(RP_Write, INFINITE);”后将阻塞在此 / 等待RP_Write信号量释放后,才能够继续向下执行。 WaitForSingleObject(RP_Write, INFINITE); / 写文件 printf(Writer thread %d begins to write to the file.n,m_serial); Sleep(m_p
25、ersist) ; / 退出线程 printf(Writer thread %d finished writing to the file.n,m_serial); /写者写完后,需要释放RP_Write信号量资源 ReleaseMutex(RP_Write); return 0;/ 读者优先处理函数/ file:文件名void ReaderPriority(char *file) int i; DWORD n_thread = 0; / 线程数目 UINT thread_ID; / 线程ID DWORD wait_for_all; / 等待所有线程结束 / 互斥对象 HANDLE h_Mutex; h_Mutex = CreateMutex(NULL,F
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1