《mfc线程汇总》由会员分享,可在线阅读,更多相关《mfc线程汇总(32页珍藏版)》请在金锄头文库上搜索。
1、MFC下的多线程编程,作者:陈帅,2008年7月30日,一、MFC 支持的两种线程:,1. 用户界面线程,通常用于处理用户输入及响应用户生成的事件和消息,并独立地相应正在应用程序其他部分执行的线程产生的消息和时间,并包含一个消息泵(a Message Pump)。用户界面线程包含一个消息处理的循环,以应对各种事件。,对于用户来说工作线程运行在后台。这就使得工作线程特别适合去等待一个事件的发生。,2. 工作线程,工作线程适用于处理那些不要求用户输入并且比较消耗时间的其 他任务(如大规模的重复计算,网络数据的发送与接受)。,注意:,在MFC应用程序中,所有的线程都是由CWinThread对象来表示
2、的; CWinThread是用户接口线程的基类,CWinApp就是CWinThread派生出来的,在编写用户接口线程时,也需要从CWinThread 类派生出自己的线程类; CWinThread同样是工作线程的基类,但在编写工作线程的时候,升值不必刻意地从CWinThread类派生出自己的线程类对象。用户可以调用MFC框架的AfxBeginThread帮助函数,会创建CWinThread对象。 在Win32API中不区分两种线程,它只需要知道线程的起始地址,就可以开始执行线程。,3.创建MFC的工作线程,(1).编程实现控制函数,一个工作线程对应一个控制函数。线程执行的任务都应编写在控制函数之
3、中。编写实现工作线程的控制函数是创建工作线程的第一步。,控制函数的原型声明是:UNIT ControlFunctionName(LPVOID pParam); 其中, UNIT ControlFunctionName:是控制函数的名字,自定。,参数pParam:是一个32位指针值,是启动工作线程时,有调用的AfxBeginThread()函数传递给工作线程的控制函数的。这个值既可以是指向简单数据类型的指针,用来传递int之类的数值,也可是是指向包含了许多参数的结构体或其他对象的指针;甚至可以忽略它。,(2).创建并启动工作线程,在进程的主线程或其他线程中调用AfxBeginThread()函数
4、就可以创建新的线程,并使线程开始运行。,AfxBeginThread()函数是MFC提供的帮助函数,有两个重载版本,区别在于使用的入口参数不同。一个用于创建并启动用户接口线程,一个用于创建并启动工作线程。,要创建并启动工作线程,必须采用如下的调用格式:,CWinThread* AfxBeginThread(,AFX_THREADPROC pfnThreadProc,LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTR
5、IBUTES lpSecurityAttrs = NULL );,参数pfnThreadProc:是一个指向工作线程的控制函数的指针,即控制函数的地址。创建工作线程是必须指定将在此线程内部运行的控制函数。,参数pParam:是一个指向某种类型的数据结构指针,执行本函数时,将把这个指针进一步传递给此线程的控制函数,使之成为线程控制函数的入口参数。,参数nPriority:通常设为0。,参数nStackSize:通常设为0。,参数dwCreateFlags :通常设为0。,参数dwCreateFlags :通常设为0。,参数lpSecurityAttrs:通常设为NULL。,(3)创建工作线程的例
6、子,struct int n; double* pD; myData; myData ss;/定义了该类型的变量,对该变量的初始化的代码省略了 UNIT MyCalcFunc(LPVOID pParam) /如果入口函数为空指针,终止线程 if( pParam=NULL) AfxEndThread(MY_NULL_POINTTER_ERROR); int n=pParam-n; /数组元素个数; double* pD=pParam-pD; /指向数组的第一个元素; double sum=0; /数组元素之和; for(int i=0; in; i+) sum+=pDi;/数组之和; CStri
7、ng bb; bb.Format(“数组的和是:%d“,sum);/格式化显示字符串; AfxMessageBox(bb); /显示结果; return 0; ,(i) 编程实现线程控制函数,(2)在程序进程的主线程中调用AfxBeginThread()函数来创建并启动运行这个线程。将控制函数名和结构变量的地址作为参数来传递,其他的参数省略,表示使用默认值。,AfxBeginThread(MyCalcFunc,一旦调用了此函数,线程就被创建,并开始执行线程函数。当数据的计算完成时,函数将停止运行,线程拥有的堆栈和其它的资源都将释放。CWinThread对象将被删除。,3. 创建并启动用户界面线
8、程,创建并启动用户界面线程一般要经过3个步骤: 第一步是从CWinThread类派生出自己的线程类; 第二步是改造这个线程类,使它能够完成用户所希望的工作; 第三步是创建并启动用户界面线程。,(1)从CWinThread类派生出自己的线程类,要创建一个MFC的用户界面线程,所要做的第一件事就是从CWinThread类派生出自己的线程类,一般借助ClassWizard来做这项工作。,(2)改造自己的线程类,(i)在这个线程类的.h头文件中,使用DECLARE_DYNCRATE宏来声明这个类;在用户线程类的.cpp实现文件中,使用IMPLEMENT_DYNCREATE宏来实现这个类。,前者的调用格
9、式是: DECLARE_DYNCRATE(class_name),其中class_name中是实际的类名。,(ii)如果在一个类中宣布使用了DECLARE_DYNCRATE宏,那么就必须在这个类的.cpp实现文件中,使用IMPLEMENT_DYNCREATE宏。它的调用格式是: IMPLEMENT_DYNCREATE(class_name, base_class_name) 参数是实际的线程类名和它的基类名。,(iii)这个线程类必须重载它的基类(CWinThread类)的某些成员函数,如该类的InitInstance()成员函数;对于基类的其它成员函数,可以有选择的重载,也可以使用缺省函数。
10、,创建用户界面线程时相关成员函数的重载,(vi) 创建新的用户界面窗口类,如窗口、对话框,并添加所需要的用户界面控件,然后建立新建的线程类与这些用户界面窗口类的联系。,(v)利用类向导,为新建的线程类添加控件成员变量,添加响应消息的成员函数,为它们编写实现的代码,经过以上步骤的改造,用户的线程类已经具备了完成用户任务的能力.,(3)创建并启用用户界面线程,要创建并启动用户界面线程,可以使用MFC提供的AfxBeginThread()函数的另一个版本,其格式是:,CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPrior
11、ity = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );,参数pThreadClass:是一个指向CRuntimeClass类对象的指针,该类是从CWinThread类继承的。用户界面线程运行时类就在第一步骤从CWinThread派生的线程类,本参数就指向它,在实际调用时,一般使用RUNTIME_CLASS 宏将线程类指针转化为指向CRuntimeClass对象的指针。 其他的参数这和创建启动工作线程时一样
12、。,4.终止线程,(1) 正常终止线程,VOID PostQuitMessage()函数的调用格式是: VOID PostQuitMessage(int nExitCode); 参数nExitCode是一个整数型值,指定一个应用程序的终止代码。,PostQuitMessage()函数发送一个WM_QUIT消息到线程的消息队列,并立即返回,没有返回值。函数只是简单地告诉系统,这个线程要求终止。当线程从它的消息队列收到一个WM_QUIT消息时,会退出它的消息循环,并将控制权返回给系统,同时把WM_QUIT消息的wParam参数中的终止代码也返回给系统,线程也就终止了。,(2)提前终止线程,要想在线
13、程尚未完成它的工作时提前终止线程,只需从线程内调用AfxEndThread函数,就可以强迫线程终止。此函数的调用格式是:,Void AfxEndThread(UNIT nExitCode);,参数nExitCode指定了线程的终止代码。,执行此函数将停止函数所在线程的执行,撤销该线程的堆栈,解除所有绑定到此线程动态链接库DLLs,并从内存中删除此线程。,(3)终止线程另一种方法:,使用Win32 API 提供的TerminateThread()函数,也可以用来终止一个正在运行的线程,但是它产生的后果是不可预料的,一般仅用来终止堆栈中的死线程,而且此函数本身不做任何内存的清除工作。,5.MFC下
14、多线程的同步机制,(1)基本概念: 在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。,下述对象是用来支持同步的: 1)信号量 2)互斥锁 3)临界区 4)事件,1)信号量,为了限制使用共享资源的线程数目,我们应该使用信号量。信号量是一个内核对象。它存储了一个计数器变量来跟踪使用共享资源的线程数目。例如,下面代码使用CSemaphore类创建了一个信号量对象,它确保在给定的时间间隔内(由构造函数第一个参数指定)最多只有5个线程能使用共享资
15、源。还假定初始时没有线程获得资源:,CSemaphore g_Sem(5, 5);,一旦线程访问共享资源,信号量的计数器就减1.若变为0,则接下来对资源的访问会被拒绝,直到有一个持有资源的线程离开(也就是说释放了信号量)。我们可以如下使用:,/ Try to use the shared resource :WaitForSingleObject(g_Sem, INFINITE); / Now the users counter of the semaphore has decremented by one / Use the shared resource / After we done,
16、let other threads use the resource :ReleaseSemaphore(g_Sem, 1, NULL); / Now the users counter of the semaphore has incremented by one,互斥锁设计为对同步访问共享资源进行保护。互斥锁在内核中实现,因此需要进入内核模式操纵它们。互斥锁不仅能在不同线程之间,也可以在不同进程之间进程同步。要跨进程使用,则互斥锁应该是有名的。MFC中使用CMutex类来操纵互斥锁。可以如下方式使用:,2)互斥锁,CSingleLock singleLock( ,3)临界区,临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此