《C++课件第16章多任务与多线程编程.ppt》由会员分享,可在线阅读,更多相关《C++课件第16章多任务与多线程编程.ppt(86页珍藏版)》请在三一办公上搜索。
1、第16章 多任务与多线程编程,C+高级编程,本章主要教学内容,进程与线程线程的种类与MFC同步类线程的使用线程的同步及常用的同步对象,第16章 多任务与多线程编程,16.1 程序、进程和线程概述16.2 线程的种类16.3 线程的创建、启动和终止16.4 线程的操作和管理16.5 在VC+环境中使用同步对象16.6 本章小结16.7 思考与练习,16.1 程序、进程和线程概述,16.1.1 多任务、进程和线程,1Windows3.x的协同多任务,如何解决后台工作和对用户的随时响应之间的协同?详见教材P27。,Windows3.x对应用程序的CPU控制权的调度方式:协同式多任务。其特点详见教材P
2、27。,补充知识:什么是模态对话框和非模态对话框?见附带文件1。,16.Windows95/NT的抢先式多任务,Windows95/NT对应用程序的CPU控制权的 调度方式:抢先式多任务。其特点详见教材P28。,16.1.1 多任务、进程和线程,3进程与线程,16.1.1 多任务、进程和线程,进程由私有虚拟地址空间、代码、数据和其它操作系统资源(如进程创建的文件、同步对象等)组成。,进程就是应用程序的运行实例。,1)什么是进程?,一个应用程序可以运行一个或多个进程。多任务就是指操作系统可以同时运行多个进程。,16.1.1 多任务、进程和线程,2)什么是线程?,一个线程可以执行程序的任意部分的代
3、码,即使这部分代码被另一个线程并发地执行。,线程是Windows95/NT操作系统分时调度中分配CPU时间的基本单位。,一个进程可以有一个或多个线程,其中一个是主线程。,一个进程的所有线程共享它的虚拟地址空间、全局变量和操作系统资源。,16.1.1 多任务、进程和线程,3)进程与线程的关系?,16.2 线程的种类,线程有两种,用户界面线程,工作者线程,MFC应用程序通过调用AfxBeginThread函数并给定不同的参数来自动创建两种线程,而不需要程序自己创建,AfxBeginThread函数的具体说明在中。,16.16.1 MFC中的线程类,1.MFC应用程序中的线程可由对象CWinThre
4、ad表示,CWinThread类派生自CcmdTarget类;,16.CWinThread对象代表在一个应用程序内运行的线程;,3.CWinThread对象允许一个应用程序拥有多个线程;,4.CWinThread对象支持两种线程类型:用户界面线程和工作者线程;,16.16.1 MFC中的线程类,5.用户界面线程可以由CWinThread类派生,也可以是CWinApp类或其派生类。但为安全起见,应由CWinThread类派生。,6.任何使用MFC的线程必须由MFC创建,创建一个线程必须调用AfxBeginThread函数。,7.CWinThread类的数据成员即成员函数见表2-1。,16.16.
5、2 用户界面线程(UI),1)用户界面线程拥有自己的消息循环来处理界面消息,具有收发消息的功能,处理从消息队列取得的消息;,2)用户界面线程通常要与用户交互;,3)用户界面线程可由CWinApp类派生(注:CWinApp类由CWinThread类派生),也可以由CWinThread类直接派生。,4)一个应用程序的主线程通常由CWinApp类派生,主线程应该是用户界面线程。,16.16.3 工作者线程,1)工作者线程没有自己的消息循环,一般用来完成后台的工作,如后台计算、打印、与其它设备的串行数据通信等,这些工作的共同特点就是耗时。,2)为了不影响主线程与用户的交互,通常耗时的工作交给工作者线程
6、来完成;,3)工作者线程可由CWinThread类直接派生。,16.3 线程的创建、启动和终止,16.3.1 线程的创建,线程的创建由AfxBeginThread函数完成。,AfxBeginThread函数有两种调用格式,可以根据需要分别用来创建工作者线程和用户界面线程。,一、AfxBeginThread函数用来创建工作者线程的调用格式:,16.3.1 线程的创建,CWinThread*AfxBeginThread(AFX_THREADPROC pfnThreadProc,LPVOID pParam,int nPriority=THREAD_PRIORITY_NORMAL,UINT nStac
7、kSize=0,DWORD dwCreateFlags=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);,pfnThreadProc:线程函数的地址,该参数不能设置为NULL,线程函数必须定义成全局函数或者类的静态成员函数。,例如:UINT myThreadFunc(LPVOID lparam)或者class Apublic:static UINT _stdcall myThreadFunc(LPVOID lparam);,1.参数说明:,16.3.1 线程的创建,pParam:要传递给线程函数的参数;,nPriority:要启动的线程的优先级,默认
8、优先级为THREAD_PRIORITY_NORMAL(普通优先级),关于线程优先级的详细说明见;,nStackSize:新线程的堆栈大小,如果设置为0,则使用默认大小,在应用程序中一般情况下线程的默认堆栈大小为1M;,16.3.1 线程的创建,lpSecurityAttrs:指向SECURITY_ATTRIBUTES结构的指针,结构中指定了线程的安全属性。如果为NULL,则与创建它的线程的安全属性相同。,dwCreateFlags:线程创建标志,该参数指定线程的初始状态,它可以被指定为下列标志:0:线程在创建后立即执行 CREATE_SUSPENDED:线程在创建后立即挂起,16.3.1 线程
9、的创建,16.函数返回值的说明:,函数AfxBeginThread返回指向CWinThread类的指针。,16.3.1 线程的创建,3.创建工作者线程的过程:,利用函数AfxBeginThread创建工作者线程需要两步:,1)编写线程控制函数;,2)调用函数AfxBeginThread启动线程,将线程控制函数的地址作为第一个参数,线程控制函数的参数作为第二个参数赋给AfxBeginThread函数,,16.3.1 线程的创建,在应用程序中,可以创建一个指向CWinThread类的指针,用来保存AfxBeginThread函数的返回值,即AfxBeginThread函数创建成功的线程类CWinT
10、hread,以便创建好的线程进行控制,例如:,4.其它说明:,16.3.1 线程的创建,CWinThread*pWinThread;pWinThread=AfxBeginThread(ControlFunction,pParam,THREAD_PRIORTY_NORMAL,0,CREATE_SUSPENDED,NULL);,16.3.1 线程的创建,pWinThread-m_bAutoDelete=false;delete pWinThread;,这时应注意:,即要将CWinThread类的数据成员m_bAutoDelete设为false,并且在退出进程前,将指向线程类CWinThread的指
11、针pWinThread删除。,16.3.1 线程的创建,二、AfxBeginThread函数用来创建用户界面线程的调用格式:,CWinThread*AfxBeginThread(CRuntimeClass*pThreadClass,int nPriority=THREAD_PRIORITY_NORMAL,UINT nStackSize=0,DWORD dwCreateFlags=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);,16.3.1 线程的创建,pfnThreadProc:指向CRuntimeClass类的指针。其它参数的说明与前面相同。,1
12、.参数说明:,16.创建用户界面线程的过程:,1)从CWinThread类派生一个新类,派生类必须用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏来声明和实现;,16.3.1 线程的创建,2)重载派生类的InitInstance、ExitInstance等函数,在InitInstance函数中添加代码。,四、通过例题演示利用AfxBeginThread函数创建工作者线程和用户界面线程的过程。,16.3.1 线程的创建,三、关于CreateThread()函数的一些说明:见附带文件2。,16.3.2 线程的启动,线程启动时的初始状态可以通过AfxBeginThrea
13、d()函数的dwCreateFlags参数指定。如下:,0:线程在创建后立即执行;CREATE_SUSPENDED:线程在创建后立即挂起;,所谓挂起就是暂停线程的执行。,16.3.3 线程的终止,遇到以下情况时,线程终止执行:,1.线程控制函数返回(即执行了return语句)。,16.线程自身调用函数ExitThread()函数即终止自己。该函数的原型如下:VOID WINAPI ExitThread(DWORD dwExitCode);该函数通过参数dwExitCode给线程设置退出码后,即终止线程的执行。,16.3.3 线程的终止,3.同一进程或其他进程的线程调用TerminateThre
14、ad函数,其原型为:BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);,该函数用来结束由hThread参数指定的线程,并把dwExitCode设成该线程的退出码。当某个线程不再响应时,我们可以用其他线程调用该函数来终止这个不响应的线程。,16.3.3 线程的终止,4.包含线程的进程被终止,如其它进程调用TerminateProcess函数终止进程的执行,或进程自身调用ExitProcess函数终止自身的执行。,BOOL WINAPI TerminateProcess(HANDLE hProcess,UINT uExitCode);,Te
15、rminateProcess函数的原型为:,VOID WINAPI ExitProcess(UINT uExitCode);,ExitProcess函数的原型为:,16.3.3 线程的终止,5.调用全局函数AfxEndThread终止进程;,注意:最好使用第1种方式终止线程,第24种方式都不宜采用。,16.4.1 线程的运行状态的设置,16.4 线程的操作和管理,1.当参数dwCreateFlags置为0时,调用AfxBeginThread函数创建的线程一启动就立即执行。这时,如果想暂停该线程的执行,可调用其成员函数SuspendedThread()函数将自身挂起。,AfxEndThread函
16、数的dwCreateFlags参数是决定线程在创建时的运行状态的。,16.4.1 线程的运行状态的设置,16.当参数dwCreateFlags置为CREATE_SUSPENDED时,调用AfxBeginThread函数创建的线程一启动就挂起,暂停执行。这时,如果想继续执行线程,可调用成员函数ResumeThread()函数唤醒被挂起的线程。,被挂起的线程不能调用此函数唤醒自身,必须由一个未被挂起的处于运行状态的线程调用此函数来取消挂起。,注意:,16.4.2 线程的优先级,一、Windows操作系统是根据进程和线程的优先级来确定它们的排队顺序并分配CPU时间的,所以对于进程和线程在其创建时要设
17、置优先级。,二、线程的优先级是根据其创建时设置的优先级和拥有该线程的进程的优先级来确定的。其最终的优先级是0到31之间的数值,数值越大,优先级越高。其中,015级是普通优先级,16 30级是实时优先级。,16.4.2 线程的优先级,Windows操作系统对具有普通优先级的线程的调度特点是:高优先级线程先运行,只有高优先级线程不运行时,才调度低优先级线程运行。优先级相同的线程按照时间片轮流运行。,三、Windows操作系统对线程的调度特点,16.4.2 线程的优先级,四、Windows操作系统对具有实时优先级的线程的调度特点是:高优先级线程先运行,只有高优先级线程不运行时,才调度低优先级线程运行
18、。优先级相同的线程不按照时间片轮转,而是先运行的线程就先控制CPU,如果它不主动放弃控制,同级或低优先级的线程就无法运行。,16.4.2 线程的优先级,五、用函数AfxBeginThread创建线程时,其参数nPriority指定新线程的优先级,该参数的取值为以下七个值之一:,THREAD_PRIORITY_TIME_CRITICALTHREAD_PRIORITY_HIGHEST THREAD_PRIORITY_ABOVE_NORMAL THREAD_PRIORITY_NORMALTHREAD_PRIORITY_BELOW_NORMALTHREAD_PRIORITY_LOWESTTHREAD_
19、PRIORITY_IDLE,16.4.2 线程的优先级,对这七个值的说明,及线程最终优先级的确定:见附带文件3。,六、如何改变线程的优先级?,在应用程序中,如果需要改变线程的优先级,可以通过线程类的成员函数SetThreadPriority()对线程的优先级重新设置。其原型如下:BOOL SetThreadPriority(int nPriority)成功执行后返回非零值,不成功返回0。,16.4.2 线程的优先级,七、如何获得线程的优先级?,在应用程序中,有时需要查询线程的优先级,这时可以通过线程类的成员函数GetThreadPriority()获得线程的优先级。其原型如下:int GetT
20、hreadPriority(HANDLE hThread)返回THREAD_PRIORITY_ERROR_RETURN 表示失败。成功时返回优先级的七个值。,16.4.2 线程的优先级,八、Windows动态调度线程的说明,线程的优先级不是一直不变的,Windows操作系统对线程从优先级进行动态调整,以保证所有的线程都能较好的运行。当线程长时间挂起以等待激活它再次运行的信号时,比它优先级低的线程将难以得到所需的CPU时间。这种情况下,当如果某线程在一段时间没有运行,Windows操作系统将提高它的优先级以保证其获得CPU时间。,16.4.3 线程间的通信,一、用简单的布尔型变量实现线程间的通信
21、 见P40例题,二、通过消息的发送和处理来实现线程和主程序之间的通信,1.调用:PostMessage(),其原型为:,16.4.3 线程间的通信,BOOLPostMessage(HWND hWnd,/要发送到的窗口的句柄 UINT Msg,/消息的ID值 WPARAM wParam,/消息的第一个参数 LPARAM lParam/消息的第二个参数);,如果执行成功返回非零值,不成功返回0。,16.4.3 线程间的通信,实现方法:,1)在头文件中定义一个消息,如线程终止的消息:const WM_THREADENDED WM_USER+10,2)加入相应的消息处理函数的声明:afx_msg LO
22、NG OnThreadended(WPARAM wParam,LPARAM lParam);,16.4.3 线程间的通信,3)在实现文件中,在消息映射部分,加入消息映射:ON_MESSGAE(WM_THREADENDED,OnThreadended),4)在线程中使用PostMessage()函数:PostMessage(HWND)pParam,WM_THREADENDED,0,0),这个语句激活相应的消息处理函数,对WM_THREADENDED消息进行处理,16.4.3 线程间的通信,16.调用CWinThread:PostThreadMessage(),其原型为:,BOOL PostThr
23、eadMessage(UINT message,/消息的ID值 WPARAM wParam,/消息的第一个参数 LPARAM lParam/消息的第二个参数);,如果执行成功返回非零值,不成功返回0。,三、使用同步类来实现线程之间的通信和控制 在16.5小节中将详细介绍,16.4.3 线程间的通信,16.5 在VC+环境中使用同步对象,16.一个进程的所有线程共享它的虚拟地址空间、全局变量和操作系统资源。,为什么要使用同步对象?,1.进程由私有虚拟地址空间、代码、数据和其它操作系统资源(如进程创建的文件、同步对象等)组成。,3.如果对多个线程之间的资源访问不加以同步控制,这些线程在共享资源时,
24、容易产生访问冲突,产生不正确的结果。,16.5 在VC+环境中使用同步对象,例如在数据库应用程序中,需要同时存在两个线程,一个负责读数据,一个负责写数据。这时就要谨防两个线程同时对数据进行操作。这时如果不进行同步控制,读线程所读取的数据,其状态是不确定的。,所以当有两个或多个线程在共享数据时,要使用同步对象以确保这多个线程不会同时访问共享资源。,16.5 在VC+环境中使用同步对象,CSyncObject,CEvent,CObject,CCriticalSection,CMutex,CSemaphore,MFC中的同步类,16.5.1 事件对象,CEvent类提供了对事件的支持。事件是一个允许
25、一个线程在某种情况发生时,唤醒另外一个线程的同步对象。事件告诉线程何时去执行某一给定的任务,从而使多个线程流平滑。,每一个CEvent对象可以有两种状态:有信号状态(signaled)和无信号状态(nonsignaled)。线程监视位于其中的CEvent类对象的状态,并在相应的时候采取相应的操作。,16.5.1 事件对象,CEvent类的成员:,构造函数CEvent,PulseEvent函数,Unlock函数,ResetEvent函数,SetEvent函数,下面分别介绍CEvent类的这些成员函数。,16.5.1 事件对象,1.构造函数CEvent的原型:,CEvent(BOOL bIniti
26、allyOwn,BOOL bManualReset,LPCTSTR lpszName,LPSECURITY_ATTRIBUTES lpsaAttribute),其参数说明如下:,16.5.1 事件对象,bInitiallyOwn:若bInitiallyOwn为TRUE,则使CMultilock类对象和CSingleLock类对象的线程可用;否则,要访问资源的线程必须等待。该参数的默认值为FALSE。,bManualReset:指定要创建的CEvent对象是属于手工事件对象还是自动事件对象。若为TRUE,则为手工事件对象,否则为自动事件对象。该参数默认值为FALSE。,补充说明:在MFC中,CE
27、vent类对象有两种类型,分别是所谓的手工事件和自动事件。对于自动事件,当其获得信号后,就会释放下一个可用的线程。一个自动 CEvent对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,释放所有可利用线程,直到调用成员函数ReSetEvent()将其设置为无信号状态时为止。注意:在创建CEvent类的对象时,默认创建的是自动事件。,16.5.1 事件对象,16.5.1 事件对象,lpszName:指定要创建的事件对象的名字,如果该事件对象将跨进程使用,则此参数不能为NULL。如果该参数和一个已经存在的CEvent对象相同,则该构造函数返回一个对这个已存在对象的引用;如
28、果参数和一个已存在的非CEvent类的同步对象(如CMutex)相同,则对象创建失败;,16.5.1 事件对象,lpsaAttribute:指向SECURITY_ATTRIBUTES结构的指针,该参数决定要创建的事件对象的安全属性,一般置为NULL。,16.5.1 事件对象,将CEvent类对象的状态设置为有信号状态,并且释放所有等待的线程;如果该事件是人工事件,则CEvent类对象保持为有信号状态,直到调用成员函数ResetEvent()将其重新设为无信号状态时为止,这样该事件就可以释放多个线程;如果CEvent类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent
29、类对象由系统自动重置为无信号状态,除非一个线程被释放。如果该函数执行成功,则返回非零值,否则返回零。,16.5.1 事件对象,3.BOOL ResetEvent(),该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。如果该函数执行成功,返回非零值,否则返回非零。,16.5.1 事件对象,4.BOOL PulseEvent(),发送一个事件脉冲,该函数完成一系列操作后才返回。对于自动事件,PulseEvent()将事件设置为有信号状态,等待一个线程被释放,将事件重置为无信号状态,然后PulseEvent
30、()返回;对于人工事件,则将等待该事件的所有线程被释放,事件被自动重置为无信号状态,然后PulseEvent()返回。一个CEvent对象在线程中被创建后,自动处于无信号状态,但在另一个线程中可以调用Win32 API WaitForSingleObject()函数来监视其状态。,16.5.1 事件对象,5.BOOL Unlock(),如果线程拥有事件对象,并且事件对象是一个自动事件对象,则返回非零值,否则返回0。,该函数用来释放事件对象,即把Lock锁定的线程解锁。,16.5.1 事件对象,另外补充一个函数 BOOL Lock(),该函数不是CEvent类的成员函数,是CEvent的父类CS
31、yncObject类的成员函数,CEvent通过继承可以使用该函数。,该函数用来锁定事件对象,阻止线程的继续执行,直到事件对象处于活动状态为止。,6.WaitForSingleObject():其原型声明如下:DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds),hHandle为指向要监视的同步对象的句柄;dwMilliseconds为监视hHandle所指向的对象所设置的超时值,单位为毫秒。,16.5.1 事件对象,当在线程的执行函数中调用该函数时,线程暂时挂起,系统监视hHandle所指向的对象的状态。如果经过dwMi
32、lliseconds毫秒后,hHandle指向的对象变为有信号状态,则WaitForSingleObject()返回,线程被释放,且返回值为WAIT_TIMEOUT;如果在挂起的dwMilliseconds毫秒内,线程所等待的对象在某一时刻变为有信号,则该函数立即返回,返回值为WAIT_OBJECT_0。,16.5.1 事件对象,参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。,16.5.1 事件对象,CEvent:ResetEvent()把对象设
33、置为无信号状态,程序在WaitForSingleObject(hHandle,INFINITE)处等待。CEvent:SetEvent()把对象设置为有信号状态,释放等待的线程。如果CEvent对象为自动事件,则当WaitForSingleObject(hHandle,INFINITE)返回时,自动把CEvent对象重置为无信号状态。,16.5.1 事件对象,总结以上,几个函数的使用顺序为:,B线程在执行到CEvent类成员函数Lock()时将会发生阻塞,而A线程此时则可以在没有B线程干扰的情况下对共享资源进行处理,并在处理完成后通过成员函数SetEvent()向B发出事件,使其被释放,得以对
34、A先前已处理完毕的共享资源进行操作。,16.5.1 事件对象,另外通过一个例题来演示事件的工作原理:,16.5.2 临界区,临界区(Critical Section)是一段代码,该代码独占对某些共享资源的访问,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。,16.5.2 临界区,在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂
35、起而进入到等待状态,并会在一定程度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。,16.5.2 临界区,虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。,MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的,只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护的代码片段即可。,16.5.2 临界区,下面通过一段代码展示了临界区在保护多线程访问的共
36、享资源中的作用。通过两个线程来分别对全局变量g_cArray10进行写入操作,用临界区对象g_clsCriticalSection来保持线程的同步,并在开启线程前对其进行初始化。为了使实验效果更加明显,体现出临界区的作用,在线程函数对共享资源g_cArray10的写入时,以Sleep()函数延迟1毫秒,使其他线程同其抢占CPU的可能性增大。如果不使用临界区对其进行保护,则共享资源数据将被破坏。而使用临界区对线程保持同步后则可以得到正确的结果。,16.5.3 互斥量,互斥量(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线
37、程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占有资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问共享资源。,图:互斥内核对象的工作模型,16.5.3 互斥量,与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。为便于理解,可参照下图。,共享资源,拥有互斥量的线程,16.5.3 互斥量,互斥对象在MFC中通过CMutex类进行表述。使用CMutex类的方法非常简单,在构造CMutex类对象的同时可以指明待查询的互斥
38、对象的名字,在构造函数返回后即可访问此互斥变量。CMutex类也是只含有构造函数这唯一的成员函数,当完成对互斥对象保护资源的访问后,可通过调用从父类CSyncObject继承的UnLock()函数完成对互斥对象的释放。,16.5.3 互斥量,CMutex类构造函数原型为:,其参数说明如下:,CMutex(BOOL bInitiallyOwn=FALSE,LPCTSTR lpszName=NULL,LPSECURITY_ATTRIBUTE SlpsaAttribute=NULL),16.5.3 互斥量,bInitiallyOwn:该参数指定是否创建了CMutex对象的线程最初拥有由互斥量CMut
39、ex控制的共享资源的控制权。,16.5.3 互斥量,lpszName:指定要创建的CMutex对象的名字。若该值为NULL,则生成CMutex对象没有命名。若与其它已经存在的CMutex对象重名,那么构造函数将创建一个参考给定名称的新CMutex对象。如果与其重名的不是CMutex对象,那么构造过程失败。,16.5.3 互斥量,lpsaAttribute:指向SECURITY_ATTRIBUTES结构的指针,该参数决定要创建的互斥量对象的安全属性,一般置为NULL。,在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。
40、,16.5.3 互斥量,先通过书中的例子来了解互斥量CMutex的工作原理,然后通过另外一个例子来加深理解。,下面给出的例子即通过互斥量对象g_clsMutex对共享内存块g_cArray进行线程的独占访问保护。,16.5.4 信号灯,信号灯又称信号量(Semaphore),这一内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。,在声明CSemaphore对象时,可以指出允许的最大资源计数和当前可用资源计数。,一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要
41、当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。,16.5.4 信号灯,16.5.4 信号灯,线程在处理完共享资源后,应在离开的同时通过Unlock()函数将当前可用资源计数加1。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。在任何时候当前可用资源计数决不可能大于最大资源计数。,16.5.4 信号灯,图:信号量对象对资源的控制,16.5.4 信号灯,信号量的使用特点使其更适用于对So
42、cket(套接字)程序中线程的同步。,下面给出一个例子。,在例子中,开启线程前首先创建了一个初始计数和最大资源计数均为2的信号量对象g_Semaphore。即在同一时刻只允许2个线程进入由g_Semaphore保护的共享资源。,16.5.4 信号灯,随后开启的三个线程均试图访问此共享资源,在前两个线程试图访问共享资源时,由于g_Semaphore的当前可用资源计数分别为2和1,此时的g_Semaphore是可以得到通知的,也就是说位于线程入口处的WaitForSingleObject()将立即返回,而在前两个线程进入到保护区域后,g_Semaphore的当前资源计数减少到0,g_Semaphore将不再得到通知,WaitForSingleObject()将线程挂起。直到此前进入到保护区的线程退出后才能得以进入。,16.5.4 信号灯,16.6 本章小结,线程与进程的基本概念与线程相关的一些概念和函数用于线程同步的对象以及MFC中的四个较为常用的线程同步类,