MFC下的多线程编程.ppt

上传人:sh****n 文档编号:3387761 上传时间:2019-12-13 格式:PPT 页数:30 大小:1.20MB
返回 下载 相关 举报
MFC下的多线程编程.ppt_第1页
第1页 / 共30页
MFC下的多线程编程.ppt_第2页
第2页 / 共30页
MFC下的多线程编程.ppt_第3页
第3页 / 共30页
点击查看更多>>
资源描述
MFC下的多线程编程,作者:陈帅,2008年7月30日,一、MFC支持的两种线程:,1.用户界面线程,通常用于处理用户输入及响应用户生成的事件和消息,并独立地相应正在应用程序其他部分执行的线程产生的消息和时间,并包含一个消息泵(aMessagePump)。用户界面线程包含一个消息处理的循环,以应对各种事件。,对于用户来说工作线程运行在后台。这就使得工作线程特别适合去等待一个事件的发生。,2.工作线程,工作线程适用于处理那些不要求用户输入并且比较消耗时间的其他任务(如大规模的重复计算,网络数据的发送与接受)。,注意:,在MFC应用程序中,所有的线程都是由CWinThread对象来表示的;CWinThread是用户接口线程的基类,CWinApp就是CWinThread派生出来的,在编写用户接口线程时,也需要从CWinThread类派生出自己的线程类;CWinThread同样是工作线程的基类,但在编写工作线程的时候,升值不必刻意地从CWinThread类派生出自己的线程类对象。用户可以调用MFC框架的AfxBeginThread帮助函数,会创建CWinThread对象。在Win32API中不区分两种线程,它只需要知道线程的起始地址,就可以开始执行线程。,3.创建MFC的工作线程,(1).编程实现控制函数,一个工作线程对应一个控制函数。线程执行的任务都应编写在控制函数之中。编写实现工作线程的控制函数是创建工作线程的第一步。,控制函数的原型声明是:UNITControlFunctionName(LPVOIDpParam);其中,UNITControlFunctionName:是控制函数的名字,自定。,参数pParam:是一个32位指针值,是启动工作线程时,有调用的AfxBeginThread()函数传递给工作线程的控制函数的。这个值既可以是指向简单数据类型的指针,用来传递int之类的数值,也可是是指向包含了许多参数的结构体或其他对象的指针;甚至可以忽略它。,(2).创建并启动工作线程,在进程的主线程或其他线程中调用AfxBeginThread()函数就可以创建新的线程,并使线程开始运行。,AfxBeginThread()函数是MFC提供的帮助函数,有两个重载版本,区别在于使用的入口参数不同。一个用于创建并启动用户接口线程,一个用于创建并启动工作线程。,要创建并启动工作线程,必须采用如下的调用格式:,CWinThread*AfxBeginThread(,AFX_THREADPROCpfnThreadProc,LPVOIDpParam,intnPriority=THREAD_PRIORITY_NORMAL,UINTnStackSize=0,DWORDdwCreateFlags=0,LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL);,参数pfnThreadProc:是一个指向工作线程的控制函数的指针,即控制函数的地址。创建工作线程是必须指定将在此线程内部运行的控制函数。,参数pParam:是一个指向某种类型的数据结构指针,执行本函数时,将把这个指针进一步传递给此线程的控制函数,使之成为线程控制函数的入口参数。,参数nPriority:通常设为0。,参数nStackSize:通常设为0。,参数dwCreateFlags:通常设为0。,参数dwCreateFlags:通常设为0。,参数lpSecurityAttrs:通常设为NULL。,(3)创建工作线程的例子,structintn;double*pD;myData;myDatass;/定义了该类型的变量,对该变量的初始化的代码省略了UNITMyCalcFunc(LPVOIDpParam)/如果入口函数为空指针,终止线程if(pParam=NULL)AfxEndThread(MY_NULL_POINTTER_ERROR);intn=pParam-n;/数组元素个数;double*pD=pParam-pD;/指向数组的第一个元素;doublesum=0;/数组元素之和;for(inti=0;in;i+)sum+=pDi;/数组之和;CStringbb;bb.Format(数组的和是:%d,sum);/格式化显示字符串;AfxMessageBox(bb);/显示结果;return0;,(i)编程实现线程控制函数,(2)在程序进程的主线程中调用AfxBeginThread()函数来创建并启动运行这个线程。将控制函数名和结构变量的地址作为参数来传递,其他的参数省略,表示使用默认值。,AfxBeginThread(MyCalcFunc,一旦调用了此函数,线程就被创建,并开始执行线程函数。当数据的计算完成时,函数将停止运行,线程拥有的堆栈和其它的资源都将释放。CWinThread对象将被删除。,3.创建并启动用户界面线程,创建并启动用户界面线程一般要经过3个步骤:第一步是从CWinThread类派生出自己的线程类;第二步是改造这个线程类,使它能够完成用户所希望的工作;第三步是创建并启动用户界面线程。,(1)从CWinThread类派生出自己的线程类,要创建一个MFC的用户界面线程,所要做的第一件事就是从CWinThread类派生出自己的线程类,一般借助ClassWizard来做这项工作。,(2)改造自己的线程类,(i)在这个线程类的.h头文件中,使用DECLARE_DYNCRATE宏来声明这个类;在用户线程类的.cpp实现文件中,使用IMPLEMENT_DYNCREATE宏来实现这个类。,前者的调用格式是:DECLARE_DYNCRATE(class_name),其中class_name中是实际的类名。,(ii)如果在一个类中宣布使用了DECLARE_DYNCRATE宏,那么就必须在这个类的.cpp实现文件中,使用IMPLEMENT_DYNCREATE宏。它的调用格式是:IMPLEMENT_DYNCREATE(class_name,base_class_name)参数是实际的线程类名和它的基类名。,(iii)这个线程类必须重载它的基类(CWinThread类)的某些成员函数,如该类的InitInstance()成员函数;对于基类的其它成员函数,可以有选择的重载,也可以使用缺省函数。,创建用户界面线程时相关成员函数的重载,(vi)创建新的用户界面窗口类,如窗口、对话框,并添加所需要的用户界面控件,然后建立新建的线程类与这些用户界面窗口类的联系。,(v)利用类向导,为新建的线程类添加控件成员变量,添加响应消息的成员函数,为它们编写实现的代码,经过以上步骤的改造,用户的线程类已经具备了完成用户任务的能力.,(3)创建并启用用户界面线程,要创建并启动用户界面线程,可以使用MFC提供的AfxBeginThread()函数的另一个版本,其格式是:,CWinThread*AfxBeginThread(CRuntimeClass*pThreadClass,intnPriority=THREAD_PRIORITY_NORMAL,UINTnStackSize=0,DWORDdwCreateFlags=0,LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL);,参数pThreadClass:是一个指向CRuntimeClass类对象的指针,该类是从CWinThread类继承的。用户界面线程运行时类就在第一步骤从CWinThread派生的线程类,本参数就指向它,在实际调用时,一般使用RUNTIME_CLASS宏将线程类指针转化为指向CRuntimeClass对象的指针。其他的参数这和创建启动工作线程时一样。,4.终止线程,(1)正常终止线程,VOIDPostQuitMessage()函数的调用格式是:VOIDPostQuitMessage(intnExitCode);参数nExitCode是一个整数型值,指定一个应用程序的终止代码。,PostQuitMessage()函数发送一个WM_QUIT消息到线程的消息队列,并立即返回,没有返回值。函数只是简单地告诉系统,这个线程要求终止。当线程从它的消息队列收到一个WM_QUIT消息时,会退出它的消息循环,并将控制权返回给系统,同时把WM_QUIT消息的wParam参数中的终止代码也返回给系统,线程也就终止了。,(2)提前终止线程,要想在线程尚未完成它的工作时提前终止线程,只需从线程内调用AfxEndThread函数,就可以强迫线程终止。此函数的调用格式是:,VoidAfxEndThread(UNITnExitCode);,参数nExitCode指定了线程的终止代码。,执行此函数将停止函数所在线程的执行,撤销该线程的堆栈,解除所有绑定到此线程动态链接库DLLs,并从内存中删除此线程。,(3)终止线程另一种方法:,使用Win32API提供的TerminateThread()函数,也可以用来终止一个正在运行的线程,但是它产生的后果是不可预料的,一般仅用来终止堆栈中的死线程,而且此函数本身不做任何内存的清除工作。,5.MFC下多线程的同步机制,(1)基本概念:在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。,下述对象是用来支持同步的:1)信号量2)互斥锁3)临界区4)事件,1)信号量,为了限制使用共享资源的线程数目,我们应该使用信号量。信号量是一个内核对象。它存储了一个计数器变量来跟踪使用共享资源的线程数目。例如,下面代码使用CSemaphore类创建了一个信号量对象,它确保在给定的时间间隔内(由构造函数第一个参数指定)最多只有5个线程能使用共享资源。还假定初始时没有线程获得资源:,CSemaphoreg_Sem(5,5);,一旦线程访问共享资源,信号量的计数器就减1.若变为0,则接下来对资源的访问会被拒绝,直到有一个持有资源的线程离开(也就是说释放了信号量)。我们可以如下使用:,/Trytousethesharedresource:WaitForSingleObject(g_Sem,INFINITE);/Nowtheuserscounterofthesemaphorehasdecrementedbyone/Usethesharedresource/Afterwedone,letotherthreadsusetheresource:ReleaseSemaphore(g_Sem,1,NULL);/Nowtheuserscounterofthesemaphorehasincrementedbyone,互斥锁设计为对同步访问共享资源进行保护。互斥锁在内核中实现,因此需要进入内核模式操纵它们。互斥锁不仅能在不同线程之间,也可以在不同进程之间进程同步。要跨进程使用,则互斥锁应该是有名的。MFC中使用CMutex类来操纵互斥锁。可以如下方式使用:,2)互斥锁,CSingleLocksingleLock(,3)临界区,临界区(CriticalSection)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。,临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。,MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的,只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。,如:/MFC临界区类对象CCriticalSectiong_clsCriticalSection;/共享资源charg_cArray10;UINTThreadProc20(LPVOIDpParam)/进入临界区g_clsCriticalSection.Lock();/对共享资源进行写入操作for(inti=0;i10;i+)g_cArrayi=a;Sleep(1);/离开临界区g_clsCriticalSection.Unlock();return0;,4)事件,一般来说,事件用于这样的情形下:当指定的动作发生后,一个线程(或多个线程)才开始执行其任务。例如,一个线程可能等待必需的数据收集完后才开始将其保存到硬盘上。,有两种事件:手动重置型和自动重置型。通过使用事件,我们可以轻松地通知另一个线程特定的动作已经发生了。对于手动重置型事件,线程使用它通知多个线程特定动作已经发生,而对于自动重置型事件,线程使用它只可以通知一个线程。,在MFC中,CEvent类封装了事件对象(若在win32中,它是用一个HANDLE来表示的)。CEvent的构造函数运行我们选择创建手动重置型和自动重置型事件。默认的创建类型是自动重置型事件。为了通知正在等待的线程,我们可以调用CEvent:SetEvent方法,这个方法将会让事件进入已通知状态。若事件是手动重置型,则事件会保持已通知状态,直到对应的CEvent:ResetEvent被调用,这个方法将使得事件进入未通知状态。这个特性使得一个线程可以通过一个SetEvent调用去通知多个线程。若事件是自动重置型,则所有正在等待的线程中只有一个线程会接收到通知。当那个线程接收到通知后,事件会自动进入未通知状态。,CEventg_eventStart;UINTThreadProc1(LPVOIDpParam):WaitForSingleObject(g_eventStart,INFINITE);return0;UINTThreadProc2(LPVOIDpParam):WaitForSingleObject(g_eventStart,INFINITE);return0;,在这个例子中,一个全局的CEvent对象被创建,它是自动重置型的。除此以外,有两个工作线程在等待这个事件对象以便开始其工作。只要第三个线程调用那个事件对象的SetEvent方法,则两个线程中之一(当然没人知道会是哪个)会接收到通知,然后事件会进入未通知状态,这就防止了第二个线程也得到事件的通知。,CEventg_eventStart(FALSE,TRUE);UINTThreadProc1(LPVOIDpParam):WaitForSingleObject(g_eventStart,INFINITE);return0;UINTThreadProc2(LPVOIDpParam):WaitForSingleObject(g_eventStart,INFINITE);return0;,这段代码和上面的稍有不同,CEvent对象构造函数的参数不一样了,但意义上就大不同了,这是一个手动重置型事件对象。若第三个线程调用事件对象的SetEvent方法,则可以确保两个工作线程都会同时(几乎是同时)开始工作。这是因为手动重置型事件在进入已通知状态后,会保持此状态直到对应的ResetEvent被调用。、除此以外事件对象还有一个方法:CEvent:PulseEvent。这个方法首先使得事件对象进入已通知状态,然后使其退回到未通知状态。若事件是手动重置型,事件进入已通知状态会让所有正在等待的线程得到通知,然后事件进入未通知状态。若事件是自动重置型,事件进入已通知状态时只会让所有等待的线程之一得到通知。若没有线程在等待,则调用ResetEvent什么也不干。,ThankYou!,
展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 图纸专区 > 课件教案


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!