C++课件-第16章-多任务与多线程编程.ppt
本章主要教学内容本章主要教学内容l进程与线程进程与线程l线程的种类与线程的种类与MFCMFC同步类同步类l线程的使用线程的使用l线程的同步及常用的同步对象线程的同步及常用的同步对象第第1616章章 多任务与多线程编程多任务与多线程编程16.1 16.1 程序、进程和线程概述程序、进程和线程概述16.2 16.2 线程的种类线程的种类16.3 16.3 线程的创建、启动和终止线程的创建、启动和终止16.4 16.4 线程的操作和管理线程的操作和管理16.5 16.5 在在VC+VC+环境中使用同步对象环境中使用同步对象16.6 16.6 本章小结本章小结16.7 16.7 思考与练习思考与练习16.1 16.1 程序、进程和线程概程序、进程和线程概述述16.1.1 16.1.1 多任务、进程和线多任务、进程和线程程1 1Windows3.xWindows3.x的协同多任务的协同多任务 如何解决后台工作和对用户的随时响应之间的如何解决后台工作和对用户的随时响应之间的协同?详见教材协同?详见教材P27P27。 Windows3.x Windows3.x对应用程序的对应用程序的CPUCPU控制权的调度方式:控制权的调度方式: 协同式多任务。其特点详见教材协同式多任务。其特点详见教材P27P27。 补充知识:什么是模态对话框和非模态对话框?补充知识:什么是模态对话框和非模态对话框? 见附带文件见附带文件1 1。16.Windows95/NT16.Windows95/NT的抢先式多任务的抢先式多任务 Windows95/NT Windows95/NT对应用程序的对应用程序的CPUCPU控制权的控制权的 调度方式:抢先式多任务。其特点详见教材调度方式:抢先式多任务。其特点详见教材P28P28。16.1.1 16.1.1 多任务、进程和线程多任务、进程和线程3 3进程与线程进程与线程16.1.1 16.1.1 多任务、进程和线多任务、进程和线程程 进程由私有虚拟地址空间、代码、数据和进程由私有虚拟地址空间、代码、数据和其它操作系统资源(如进程创建的文件、同步其它操作系统资源(如进程创建的文件、同步对象等)组成。对象等)组成。 进程就是应用程序的运行实例。进程就是应用程序的运行实例。1)1)什么是进程?什么是进程? 一个应用程序可以运行一个或多个进程。多任一个应用程序可以运行一个或多个进程。多任务就是指操作系统可以同时运行多个进程。务就是指操作系统可以同时运行多个进程。 16.1.1 16.1.1 多任务、进程和线多任务、进程和线程程2)2)什么是线程?什么是线程? 一个线程可以执行程序的任意部分的代码,一个线程可以执行程序的任意部分的代码,即使这部分代码被另一个线程并发地执行。即使这部分代码被另一个线程并发地执行。 线程是线程是Windows95/NTWindows95/NT操作系统分时调度中分操作系统分时调度中分配配CPUCPU时间的基本单位。时间的基本单位。 一个进程可以有一个或多个线程,其中一个一个进程可以有一个或多个线程,其中一个是主线程。是主线程。 一个进程的所有线程共享它的虚拟地址空间一个进程的所有线程共享它的虚拟地址空间、全局变量和操作系统资源。、全局变量和操作系统资源。16.1.1 16.1.1 多任务、进程和线多任务、进程和线程程3)3)进程与线程的关系?进程与线程的关系?16.2 16.2 线程的种类线程的种类线程有两种线程有两种用户界面线程用户界面线程工作者线程工作者线程 MFC MFC应用程序通过调用应用程序通过调用AfxBeginThreadAfxBeginThread函数并给定函数并给定不同的参数来自动创建两种线程,而不需要程序自己不同的参数来自动创建两种线程,而不需要程序自己创建,创建, AfxBeginThreadAfxBeginThread函数的具体说明在函数的具体说明在16.3.116.3.1中。中。16.16.1 MFC16.16.1 MFC中的线程中的线程类类1.1. MFC MFC应用程序中的线程可由对象应用程序中的线程可由对象CWinThreadCWinThread表示,表示,CWinThreadCWinThread类派生自类派生自CcmdTargetCcmdTarget类;类;16.16. CWinThread CWinThread对象代表在一个应用程序内运行的线程对象代表在一个应用程序内运行的线程;3.3. CWinThread CWinThread对象允许一个应用程序拥有多个线程;对象允许一个应用程序拥有多个线程;4.4. CWinThread CWinThread对象支持两种线程类型:用户界面线程对象支持两种线程类型:用户界面线程和工作者线程和工作者线程; ;16.16.1 MFC16.16.1 MFC中的线程中的线程类类5.5. 用户界面线程可以由用户界面线程可以由CWinThreadCWinThread类派生,也类派生,也可以是可以是CWinAppCWinApp类或其派生类。但为安全起见,类或其派生类。但为安全起见,应由应由CWinThreadCWinThread类派生。类派生。6.6. 任何使用任何使用MFCMFC的线程必须由的线程必须由MFCMFC创建,创建一个创建,创建一个线程必须调用线程必须调用AfxBeginThreadAfxBeginThread函数。函数。7.7. CWinThread CWinThread类的数据成员即成员函数见表类的数据成员即成员函数见表2-12-1。16.16.2 16.16.2 用户界面线程(用户界面线程(UIUI)1)1) 用户界面线程拥有自己的消息循环来处理界面消息,用户界面线程拥有自己的消息循环来处理界面消息,具有收发消息的功能,处理从消息队列取得的消息;具有收发消息的功能,处理从消息队列取得的消息;2)2) 用户界面线程通常要与用户交互;用户界面线程通常要与用户交互;3)3) 用户界面线程可由用户界面线程可由CWinAppCWinApp类派生类派生( (注:注:CWinAppCWinApp类由类由CWinThreadCWinThread类派生类派生) ),也可以由,也可以由CWinThreadCWinThread类直接派生。类直接派生。4)4) 一个应用程序的主线程通常由一个应用程序的主线程通常由CWinAppCWinApp类派生,主类派生,主线程应该是用户界面线程。线程应该是用户界面线程。16.16.3 16.16.3 工作者线工作者线程程1)1) 工作者线程没有自己的消息循环,一般用来完成工作者线程没有自己的消息循环,一般用来完成后台的工作,如后台计算、打印、与其它设备的串后台的工作,如后台计算、打印、与其它设备的串行数据通信等,这些工作的共同特点就是耗时。行数据通信等,这些工作的共同特点就是耗时。2)2) 为了不影响主线程与用户的交互,通常耗时的工为了不影响主线程与用户的交互,通常耗时的工作交给工作者线程来完成;作交给工作者线程来完成;3)3) 工作者线程可由工作者线程可由CWinThreadCWinThread类直接派生。类直接派生。16.3 16.3 线程的创建、启动和终线程的创建、启动和终止止16.3.1 16.3.1 线程的创建线程的创建线程的创建由线程的创建由AfxBeginThreadAfxBeginThread函数完成。函数完成。 AfxBeginThread AfxBeginThread函数有两种调用格式,可以根据函数有两种调用格式,可以根据需要分别用来创建工作者线程和用户界面线程。需要分别用来创建工作者线程和用户界面线程。一、一、AfxBeginThreadAfxBeginThread函数用来创建工作者线程的函数用来创建工作者线程的调用格式:调用格式:16.3.1 16.3.1 线程的创建线程的创建CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ); pfnThreadProc:pfnThreadProc:线程函数的地址线程函数的地址, ,该参数不能设该参数不能设置为置为NULL,NULL,线程函数必须定义成全局函数或者类线程函数必须定义成全局函数或者类的静态成员函数。的静态成员函数。例如例如:UINT myThreadFunc(LPVOID lparam)或者或者class Apublic: static UINT _stdcall myThreadFunc(LPVOID lparam);1.1.参数说明参数说明: :16.3.1 16.3.1 线程的创线程的创建建 pParam: pParam:要传递给线程函数的参数;要传递给线程函数的参数; nPriority:nPriority:要启动的线程的优先级要启动的线程的优先级, ,默认优先级默认优先级为为THREAD_PRIORITY_NORMAL(THREAD_PRIORITY_NORMAL(普通优先级普通优先级),),关于关于线程优先级的详细说明见线程优先级的详细说明见16.4.216.4.2;nStackSize:nStackSize:新线程的堆栈大小新线程的堆栈大小, ,如果设置为如果设置为0,0,则使则使用默认大小用默认大小, ,在应用程序中一般情况下线程的默认堆在应用程序中一般情况下线程的默认堆栈大小为栈大小为1M1M;16.3.1 16.3.1 线程的创线程的创建建 lpSecurityAttrslpSecurityAttrs : :指向指向SECURITY_ATTRIBUTESSECURITY_ATTRIBUTES结结构的指针,结构中指定了线程的安全属性。如果构的指针,结构中指定了线程的安全属性。如果为为NULLNULL,则与,则与 创建它的线程的安全属性相同。创建它的线程的安全属性相同。 dwCreateFlags:dwCreateFlags:线程创建标志线程创建标志, ,该参数指定线程该参数指定线程的初始状态,它可以被指定为下列标志:的初始状态,它可以被指定为下列标志: 0 0:线程在创建后立即执行:线程在创建后立即执行 CREATE_SUSPENDED:CREATE_SUSPENDED:线程在创建后立即挂起线程在创建后立即挂起16.3.1 16.3.1 线程的创线程的创建建16.16.函数返回值的说明函数返回值的说明: :函数函数AfxBeginThreadAfxBeginThread返回指向返回指向CWinThreadCWinThread类的指针。类的指针。16.3.1 16.3.1 线程的创线程的创建建3.3.创建工作者线程的过程创建工作者线程的过程: :利用函数利用函数AfxBeginThreadAfxBeginThread创建工作者线程需要两步:创建工作者线程需要两步:1) 1) 编写线程控制函数;编写线程控制函数;2) 2) 调用函数调用函数AfxBeginThreadAfxBeginThread启动线程,将线程控启动线程,将线程控制函数的地址作为第一个参数,线程控制函数的参制函数的地址作为第一个参数,线程控制函数的参数作为数作为第二个参数赋给第二个参数赋给AfxBeginThreadAfxBeginThread函数,函数,16.3.1 16.3.1 线程的创线程的创建建 在应用程序中,可以创建一个指向在应用程序中,可以创建一个指向CWinThreadCWinThread类类的指针,用来保存的指针,用来保存AfxBeginThreadAfxBeginThread函数的返回值,即函数的返回值,即AfxBeginThreadAfxBeginThread函数创建成功的线程类函数创建成功的线程类CWinThreadCWinThread,以便创建好的线程进行控制,例如:以便创建好的线程进行控制,例如:4.4.其它说明其它说明: :16.3.1 16.3.1 线程的创线程的创建建CWinThreadCWinThread* * pWinThread; pWinThread;pWinThread=AfxBeginThread(pWinThread=AfxBeginThread( ControlFunction,ControlFunction, pParam, pParam, THREAD_PRIORTY_NORMAL, THREAD_PRIORTY_NORMAL, 0, 0, CREATE_SUSPENDED, CREATE_SUSPENDED, NULL); NULL);16.3.1 16.3.1 线程的创线程的创建建pWinThread-m_bAutoDelete=false;delete pWinThread;这时应注意这时应注意: : 即要将即要将CWinThreadCWinThread类的数据成员类的数据成员m_bAutoDeletem_bAutoDelete设为设为false,false,并且在退出进程前,将指向线程类并且在退出进程前,将指向线程类CWinThreadCWinThread的指针的指针pWinThreadpWinThread删除。删除。16.3.1 16.3.1 线程的创线程的创建建二、二、AfxBeginThreadAfxBeginThread函数用来创建用户界面线程函数用来创建用户界面线程的调用格式:的调用格式:CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );16.3.1 16.3.1 线程的创线程的创建建 pfnThreadProc: pfnThreadProc: 指向指向CRuntimeClassCRuntimeClass类的指针。类的指针。 其它参数的说明与前面相同。其它参数的说明与前面相同。1.1.参数说明参数说明: :16.16.创建用户界面线程的过程创建用户界面线程的过程: :1) 1) 从从CWinThreadCWinThread类派生一个新类,派生类必须类派生一个新类,派生类必须用用DECLARE_DYNCREATEDECLARE_DYNCREATE和和IMPLEMENT_DYNCREATEIMPLEMENT_DYNCREATE宏宏来声明和实现;来声明和实现;16.3.1 16.3.1 线程的创线程的创建建2) 2) 重载派生类的重载派生类的InitInstanceInitInstance、ExitInstanceExitInstance等等函数,在函数,在InitInstanceInitInstance函数中添加代码。函数中添加代码。四、四、通过例题演示利用通过例题演示利用AfxBeginThreadAfxBeginThread函数创建函数创建工作者线程和用户界面线程的过程。工作者线程和用户界面线程的过程。16.3.1 16.3.1 线程的创线程的创建建三、三、关于关于CreateThread( )CreateThread( )函数的一些说明:函数的一些说明: 见附带文件见附带文件2 2。16.3.2 16.3.2 线程的启动线程的启动 线程启动时的初始状态可以通过线程启动时的初始状态可以通过AfxBeginThread( )AfxBeginThread( )函数的函数的dwCreateFlagsdwCreateFlags参数指定。如下:参数指定。如下:0 0:线程在创建后立即执行;:线程在创建后立即执行;CREATE_SUSPENDED:CREATE_SUSPENDED:线程在创建后立即挂起;线程在创建后立即挂起;所谓挂起就是暂停线程的执行。所谓挂起就是暂停线程的执行。16.3.3 16.3.3 线程的终线程的终止止遇到以下情况时,线程终止执行:遇到以下情况时,线程终止执行:1.1.线程控制函数返回(即执行了线程控制函数返回(即执行了returnreturn语句)。语句)。16.16.线程自身调用函数线程自身调用函数ExitThread( )ExitThread( )函数函数即终止自己即终止自己。该函数的原型如下:该函数的原型如下: VOID WINAPI ExitThread(DWORD dwExitCode );VOID WINAPI ExitThread(DWORD dwExitCode ); 该函数通过参数该函数通过参数dwExitCodedwExitCode给线程设置退出码后,给线程设置退出码后,即终止线程的执行。即终止线程的执行。16.3.3 16.3.3 线程的终线程的终止止3.3.同一进程或其他进程的线程调用同一进程或其他进程的线程调用TerminateThreadTerminateThread函数,其原型为:函数,其原型为: BOOL TerminateThread( BOOL TerminateThread( HANDLE hThread, HANDLE hThread, DWORD dwExitCode DWORD dwExitCode ); ); 该函数用来结束由该函数用来结束由hThreadhThread参数指定的线程,并把参数指定的线程,并把dwExitCodedwExitCode设成该线程的退出码。当某个线程不再响应时,设成该线程的退出码。当某个线程不再响应时,我们可以用其他线程调用该函数来终止这个不响应的线程。我们可以用其他线程调用该函数来终止这个不响应的线程。 16.3.3 16.3.3 线程的终线程的终止止4.4.包含包含线程的进程被终止,如其它进程调用线程的进程被终止,如其它进程调用TerminateProcessTerminateProcess函数终止进程的执行,或进函数终止进程的执行,或进程自身调用程自身调用ExitProcessExitProcess函数终止自身的执行。函数终止自身的执行。BOOL WINAPI TerminateProcess(BOOL WINAPI TerminateProcess( HANDLE hProcess, HANDLE hProcess, UINT uExitCode UINT uExitCode ); ); TerminateProcessTerminateProcess函数的原型为:函数的原型为:VOID WINAPI ExitProcess( UINT uExitCode ); VOID WINAPI ExitProcess( UINT uExitCode ); ExitProcess函数的原型为:函数的原型为:16.3.3 16.3.3 线程的终线程的终止止5.5. 调用全局函数调用全局函数AfxEndThreadAfxEndThread终止进程;终止进程;注意:最好使用第注意:最好使用第1 1种方式终止线程,第种方式终止线程,第2424种种方式都不宜采用。方式都不宜采用。16.4.1 16.4.1 线程的运行状态的设置线程的运行状态的设置16.4 16.4 线程的操作和管理线程的操作和管理1.1.当参数当参数dwCreateFlagsdwCreateFlags置为置为0 0时,调用时,调用AfxBeginThreadAfxBeginThread函数创建的线程一启动就立即执行。函数创建的线程一启动就立即执行。这时,如果想暂停该线程的执行,可调用其成员函数这时,如果想暂停该线程的执行,可调用其成员函数SuspendedThread( )SuspendedThread( )函数将自身挂起。函数将自身挂起。 AfxEndThread AfxEndThread函数的函数的dwCreateFlagsdwCreateFlags参数是决定参数是决定线程在创建时的运行状态的。线程在创建时的运行状态的。16.4.1 16.4.1 线程的运行状态的设置线程的运行状态的设置16.16.当参数当参数dwCreateFlagsdwCreateFlags置为置为CREATE_SUSPENDEDCREATE_SUSPENDED时时,调用,调用AfxBeginThreadAfxBeginThread函数创建的线程一启动就挂函数创建的线程一启动就挂起,暂停执行。这时,如果想继续执行线程,可调起,暂停执行。这时,如果想继续执行线程,可调用成员函数用成员函数ResumeThread( )ResumeThread( )函数唤醒被挂起的线程函数唤醒被挂起的线程。 被挂起的线程不能调用此函数唤醒自身,必须被挂起的线程不能调用此函数唤醒自身,必须由一个未被挂起的处于运行状态的线程调用此函数由一个未被挂起的处于运行状态的线程调用此函数来取消挂起。来取消挂起。注意:注意:16.4.2 16.4.2 线程的优先线程的优先级级一、一、 WindowsWindows操作系统是根据进程和线程的优先级操作系统是根据进程和线程的优先级来确定它们的排队顺序并分配来确定它们的排队顺序并分配CPUCPU时间的,所以对时间的,所以对于进程和线程在其创建时要设置优先级。于进程和线程在其创建时要设置优先级。二、二、 线程的优先级是根据其创建时设置的优先级线程的优先级是根据其创建时设置的优先级和拥有该线程的进程的优先级来确定的。其最终的和拥有该线程的进程的优先级来确定的。其最终的优先级是优先级是0 0到到3131之间的数值,数值越大,优先级越之间的数值,数值越大,优先级越高。其中,高。其中,0 01515级是普通优先级,级是普通优先级,1616 3030级是实级是实时优先级。时优先级。16.4.2 16.4.2 线程的优先线程的优先级级WindowsWindows操作系统对具有操作系统对具有普通优先级普通优先级的线程的调度的线程的调度特点是:特点是: 高优先级线程先运行,只有高优先级线程不运高优先级线程先运行,只有高优先级线程不运行时,才调度低优先级线程运行。优先级相同的线行时,才调度低优先级线程运行。优先级相同的线程按照时间片轮流运行。程按照时间片轮流运行。三、三、WindowsWindows操作系统对线程的调度特点操作系统对线程的调度特点16.4.2 16.4.2 线程的优先线程的优先级级四、四、WindowsWindows操作系统对具有实时优先级的线程的操作系统对具有实时优先级的线程的调度特点是:调度特点是: 高优先级线程先运行,只有高优先级线程不运高优先级线程先运行,只有高优先级线程不运行时,才调度低优先级线程运行。优先级相同的线行时,才调度低优先级线程运行。优先级相同的线程不按照时间片轮转,而是先运行的线程就先控制程不按照时间片轮转,而是先运行的线程就先控制CPUCPU,如果它不主动放弃控制,同级或低优先级的,如果它不主动放弃控制,同级或低优先级的线程就无法运行。线程就无法运行。16.4.2 16.4.2 线程的优先线程的优先级级五、五、 用函数用函数AfxBeginThreadAfxBeginThread创建线程时,其参数创建线程时,其参数nPrioritynPriority指定新线程的优先级,该参数的取值为指定新线程的优先级,该参数的取值为以下七个值之一:以下七个值之一:lTHREAD_PRIORITY_TIME_CRITICALlTHREAD_PRIORITY_HIGHEST lTHREAD_PRIORITY_ABOVE_NORMAL lTHREAD_PRIORITY_NORMALlTHREAD_PRIORITY_BELOW_NORMALlTHREAD_PRIORITY_LOWESTlTHREAD_PRIORITY_IDLE 16.4.2 16.4.2 线程的优先线程的优先级级 对这七个值的说明,及线程最终优先级的确定:对这七个值的说明,及线程最终优先级的确定:见附带文件见附带文件3 3。六、如何改变线程的优先级?六、如何改变线程的优先级? 在应用程序中,如果需要改变线程的优先级,可在应用程序中,如果需要改变线程的优先级,可以通过线程类的成员函数以通过线程类的成员函数SetThreadPriority( )SetThreadPriority( )对线对线程的优先级重新设置。其原型如下:程的优先级重新设置。其原型如下: BOOL SetThreadPriorityBOOL SetThreadPriority(int nPriorityint nPriority) 成功执行后返回非零值,不成功返回成功执行后返回非零值,不成功返回0 0。 16.4.2 16.4.2 线程的优先线程的优先级级七、如何获得线程的优先级?七、如何获得线程的优先级? 在应用程序中,有时需要查询线程的优先级,这在应用程序中,有时需要查询线程的优先级,这时可以通过线程类的成员函数时可以通过线程类的成员函数GetThreadPriority( )GetThreadPriority( )获得线程的优先级。其原型如下:获得线程的优先级。其原型如下: int GetThreadPriority( HANDLE hThread )int GetThreadPriority( HANDLE hThread ) 返回返回THREAD_PRIORITY_ERROR_RETURN THREAD_PRIORITY_ERROR_RETURN 表示失败表示失败。成功时返回优先级的七个值。成功时返回优先级的七个值。 16.4.2 16.4.2 线程的优先线程的优先级级八、八、WindowsWindows动态调度线程的说明动态调度线程的说明 线程的优先级不是一直不变的,线程的优先级不是一直不变的,WindowsWindows操作系操作系统对线程从优先级进行动态调整,以保证所有的线统对线程从优先级进行动态调整,以保证所有的线程都能较好的运行。程都能较好的运行。 当线程长时间挂起以等待激活它再次运行的信当线程长时间挂起以等待激活它再次运行的信号时,比它优先级低的线程将难以得到所需的号时,比它优先级低的线程将难以得到所需的CPUCPU时时间。这种情况下,当如果某线程在一段时间没有运间。这种情况下,当如果某线程在一段时间没有运行,行,WindowsWindows操作系统将提高它的优先级以保证其获操作系统将提高它的优先级以保证其获得得CPUCPU时间。时间。16.4.3 16.4.3 线程间的通线程间的通信信一、一、用简单的布尔型变量实现线程间的通信用简单的布尔型变量实现线程间的通信 见见P40P40例题例题二、二、通过消息的发送和处理来实现线程和主程序通过消息的发送和处理来实现线程和主程序之间的通信之间的通信1.1.调用调用:PostMessage( ):PostMessage( ),其原型为:,其原型为:16.4.3 16.4.3 线程间的通线程间的通信信BOOL PostMessage( BOOL PostMessage( HWND hWnd, / HWND hWnd, /要发送到的窗口的句柄要发送到的窗口的句柄 UINT Msg, / UINT Msg, /消息的消息的IDID值值 WPARAM wParam, / WPARAM wParam, /消息的第一个参数消息的第一个参数 LPARAM lParam / LPARAM lParam /消息的第二个参数消息的第二个参数 ); ); 如果执行成功返回非零值,不成功返回如果执行成功返回非零值,不成功返回0 0。16.4.3 16.4.3 线程间的通线程间的通信信实现方法:实现方法:1)1)在头文件中定义一个消息,如线程终止的消息:在头文件中定义一个消息,如线程终止的消息: const WM_THREADENDED WM_USER+10const WM_THREADENDED WM_USER+102)2)加入相应的消息处理函数的声明:加入相应的消息处理函数的声明: afx_msg LONG OnThreadendedafx_msg LONG OnThreadended( WPARAM wParamWPARAM wParam, LPARAM lParamLPARAM lParam););16.4.3 16.4.3 线程间的通线程间的通信信3)3)在实现文件中,在消息映射部分,加入消息映射:在实现文件中,在消息映射部分,加入消息映射: ON_MESSGAE(WM_THREADENDED,OnThreadendedON_MESSGAE(WM_THREADENDED,OnThreadended)4)4)在线程中使用在线程中使用PostMessage( )PostMessage( )函数:函数: PostMessage(HWND)pParam,WM_THREADENDED,0,0)PostMessage(HWND)pParam,WM_THREADENDED,0,0) 这个语句激活相应的消息处理函数,对这个语句激活相应的消息处理函数,对WM_THREADENDEDWM_THREADENDED消息进行处理消息进行处理16.4.3 16.4.3 线程间的通线程间的通信信16.16.调用调用CWinThread:PostThreadMessage( )CWinThread:PostThreadMessage( ), 其原型为:其原型为:BOOL PostThreadMessage( BOOL PostThreadMessage( UINT message, / UINT message, /消息的消息的IDID值值 WPARAM wParam, / WPARAM wParam, /消息的第一个参数消息的第一个参数 LPARAM lParam / LPARAM lParam /消息的第二个参数消息的第二个参数); ); 如果执行成功返回非零值,不成功返回如果执行成功返回非零值,不成功返回0 0。三、三、使使用同步类来实现线程之间的通信和控制用同步类来实现线程之间的通信和控制 在在16.516.5小节中将详细介绍小节中将详细介绍16.4.3 16.4.3 线程间的通线程间的通信信16.5 16.5 在在VC+VC+环境中使用同步对象环境中使用同步对象16.16.一个进程的所有线程共享它的虚拟地址空间、全局一个进程的所有线程共享它的虚拟地址空间、全局变量和操作系统资源。变量和操作系统资源。为什么要使用同步对象?为什么要使用同步对象?1.1.进程由私有虚拟地址空间、代码、数据和其它操作系进程由私有虚拟地址空间、代码、数据和其它操作系统资源(如进程创建的文件、同步对象等)组成。统资源(如进程创建的文件、同步对象等)组成。 3.3.如果对多个线程之间的资源访问不加以同步控制,如果对多个线程之间的资源访问不加以同步控制,这些线程在共享资源时,容易产生访问冲突,产生不这些线程在共享资源时,容易产生访问冲突,产生不正确的结果。正确的结果。16.5 16.5 在在VC+VC+环境中使用同步对象环境中使用同步对象 例如在数据库应用程序中,需要同时存在两个线例如在数据库应用程序中,需要同时存在两个线程,一个负责读数据,一个负责写数据。这时就要谨程,一个负责读数据,一个负责写数据。这时就要谨防两个线程同时对数据进行操作。这时如果不进行同防两个线程同时对数据进行操作。这时如果不进行同步控制,读线程所读取的数据,其状态是不确定的。步控制,读线程所读取的数据,其状态是不确定的。 所以当有两个或多个线程在共享数据时,要使用所以当有两个或多个线程在共享数据时,要使用同步对象以确保这多个线程不会同时访问共享资源。同步对象以确保这多个线程不会同时访问共享资源。16.5 16.5 在在VC+VC+环境中使用同步对象环境中使用同步对象CSyncObject CEvent CObject CCriticalSection CMutexCSemaphoreMFC中的同步类中的同步类16.5.1 16.5.1 事件对象事件对象 CEvent CEvent类提供了对事件的支持。事件是一个允类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。事件告诉线程何时去执行某一给定的的同步对象。事件告诉线程何时去执行某一给定的任务,从而使多个线程流平滑。任务,从而使多个线程流平滑。 每一个每一个CEventCEvent对象可以有两种状态:有信号状对象可以有两种状态:有信号状态态(signaled)(signaled)和无信号状态和无信号状态(nonsignaled)(nonsignaled)。线程监。线程监视位于其中的视位于其中的CEventCEvent类对象的状态,并在相应的时类对象的状态,并在相应的时候采取相应的操作。候采取相应的操作。16.5.1 16.5.1 事件对象事件对象 CEventCEvent类的成员:类的成员: 构造函数构造函数CEventCEvent PulseEvent PulseEvent函数函数 Unlock Unlock函数函数 ResetEvent ResetEvent函数函数 SetEvent SetEvent函数函数下面分别介绍下面分别介绍CEventCEvent类的这些成员函数。类的这些成员函数。16.5.1 16.5.1 事件对象事件对象1.1.构造函数构造函数CEventCEvent的原型的原型: :CEvent( BOOL bInitiallyOwn , BOOL bManualReset , LPCTSTR lpszName , LPSECURITY_ATTRIBUTES lpsaAttribute )其参数说明如下:其参数说明如下:16.5.1 16.5.1 事件对象事件对象 bInitiallyOwn bInitiallyOwn: 若若bInitiallyOwnbInitiallyOwn为为TRUETRUE,则使,则使CMultilockCMultilock类对象类对象和和CSingleLockCSingleLock类对象的线程可用;否则,要访问资源类对象的线程可用;否则,要访问资源的线程必须等待。该参数的默认值为的线程必须等待。该参数的默认值为FALSEFALSE。 bManualReset bManualReset: 指定要创建的指定要创建的CEventCEvent对象是属于手工事件对象还对象是属于手工事件对象还是自动事件对象。若为是自动事件对象。若为TRUETRUE,则为手工事件对象,否,则为手工事件对象,否则为自动事件对象。该参数默认值为则为自动事件对象。该参数默认值为FALSEFALSE。 补充说明:补充说明:在在MFCMFC中,中,CEventCEvent类对象有两种类类对象有两种类型,分别是所谓的手工事件和自动事件。对于自动型,分别是所谓的手工事件和自动事件。对于自动事件,当其获得信号后,就会释放下一个可用的线事件,当其获得信号后,就会释放下一个可用的线程。一个自动程。一个自动 CEventCEvent对象在被至少一个线程释放对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得后会自动返回到无信号状态;而人工事件对象获得信号后,释放所有可利用线程,直到调用成员函数信号后,释放所有可利用线程,直到调用成员函数ReSetEvent()ReSetEvent()将其设置为无信号状态时为止。将其设置为无信号状态时为止。 注意:注意:在创建在创建CEventCEvent类的对象时,默认创建的类的对象时,默认创建的是自动事件。是自动事件。 16.5.1 16.5.1 事件对象事件对象16.5.1 16.5.1 事件对象事件对象lpszName