ImageVerifierCode 换一换
格式:DOCX , 页数:16 ,大小:27.57KB ,
资源ID:7653616      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/7653616.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(MFC多线程及线程同步.docx)为本站会员(b****5)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

MFC多线程及线程同步.docx

1、MFC多线程及线程同步MFC 多线程及线程同步 一、MFC对多线程编程的支持MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该

2、函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:(1) CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UNT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );/用于创建工作者线程PfnThreadProc:指向工作者线程的执行函数的指针,线程函数

3、原型必须声明如下: UINT ExecutingFunction(LPVOID pParam);请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。 pParam: 一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略; nPriority: 线程的优先级。如果为0,则线程与其父线程具有相同的优先级; nStackSize: 线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小; dwCreateFlags:

4、如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起; lpSecurityAttrs:线程的安全属性指针,一般为NULL; (2) CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UNT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );pThreadClass 是指向 CWin

5、Thread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,在以后的例子中我们将发现同主线程的机制几乎一样。下面我们对CWinThread类的数据成员及常用函数进行简要说明。 m_hThread: 当前线程的句柄; m_nThreadID: 当前线程的ID; m_pMainWnd:指向应用程序主窗口的指针 BOOL CWinThread:CreateThread(DWORD dwCreateFlags=0,UINT nStackSize=0,LPSECURITY_ATTRIBUTES lpS

6、ecurityAttrs=NULL);该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数和API函数CreateThread中的对应参数有相同含义,该函数执行成功,返回非0值,否则返回0。一般情况下,调用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。virtual BOOL CWinThread:InitInstance();重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返

7、回0。用户界面线程经常重载该函数,工作者线程一般不使用InitInstance()。 virtual int CWinThread:ExitInstance();在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同InitInstance()成员函数一样,该函数也只适用于用户界面线程。二、MFC中线程同步在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。如果不采取适当的措施,其他

8、线程往往会在线程处理任务结束前就去访问处理结果,这就很有可能得到有关处理结果的错误了解。例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。为了确保读线程读取到的是经过修改的变量,就必须在向变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。象这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法

9、。其特点是同步速度特别快,适合于对线程运行速度有严格要求的场合。内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。1.临界区临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他

10、线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。代码 CRITICAL_SECTIONg_cs; /临界区结构对象charg_

11、cArray10; /共享资源UINTThreadProc10(LPVOIDpParam)EnterCriticalSection(&g_cs);/进入临界区for(inti=0;i10;i+)/对共享资源进行写入操作g_cArrayi=a;Sleep(1);LeaveCriticalSection(&g_cs);/离开临界区return0;UINTThreadProc11(LPVOIDpParam)EnterCriticalSection(&g_cs);for(inti=0;i10;i+)g_cArray10-i-1=b;Sleep(1);LeaveCriticalSection(&g_cs

12、);return0;voidCSample08View:OnCriticalSection()InitializeCriticalSection(&g_cs); /初始化临界区AfxBeginThread(ThreadProc10,NULL); /启动线程AfxBeginThread(ThreadProc11,NULL);Sleep(300);CStringsResult=CString(g_cArray);AfxMessageBox(sResult);在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定

13、程度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处

14、理是非常简单的,只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。对于上述代码,可通过CCriticalSection类将其改写如下: 代码 CCriticalSectiong_clsCriticalSection;/MFC临界区类对象charg_cArray10; /共享资源UINTThreadProc20(LPVOIDpParam)g_clsCriticalSection.Lock(); /进入临界区for(inti=0;i10;i+)/对共享资源进行写入操作g_cArrayi=a;Sleep(1);g_clsCritica

15、lSection.Unlock();/离开临界区return0;UINTThreadProc21(LPVOIDpParam)g_clsCriticalSection.Lock();for(inti=0;i10;i+)g_cArray10-i-1=b;Sleep(1);g_clsCriticalSection.Unlock();return0;voidCSample08View:OnCriticalSectionMfc()AfxBeginThread(ThreadProc20,NULL);AfxBeginThread(ThreadProc21,NULL);Sleep(300);CStringsR

16、esult=CString(g_cArray);AfxMessageBox(sResult);2.事件内核对象在前面讲述线程通信时曾使用过事件内核对象来进行线程间的通信,除此之外,事件内核对象也可以通过通知操作的方式来保持线程的同步。对于前面那段使用临界区保持线程同步的代码可用事件对象的线程同步方法改写如下:代码 HANDLEhEvent=NULL;/事件句柄charg_cArray10;/共享资源UINTThreadProc12(LPVOIDpParam)WaitForSingleObject(hEvent,INFINITE);/等待事件置位for(inti=0;i10;i+)g_cArra

17、yi=a;Sleep(1);SetEvent(hEvent);/处理完成后即将事件对象置位return0;UINTThreadProc13(LPVOIDpParam)WaitForSingleObject(hEvent,INFINITE);for(inti=0;i10;i+)g_cArray10-i-1=b;Sleep(1);SetEvent(hEvent);return0;voidCSample08View:OnEvent()hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);/创建事件SetEvent(hEvent);/事件置位AfxBeginThread(

18、ThreadProc12,NULL);/启动线程AfxBeginThread(ThreadProc13,NULL);Sleep(300);CStringsResult=CString(g_cArray);AfxMessageBox(sResult);在创建线程前,首先创建一个可以自动复位的事件内核对象hEvent,而线程函数则通过WaitForSingleObject()等待函数无限等待hEvent的置位,只有在事件置位时WaitForSingleObject()才会返回,被保护的代码将得以执行。对于以自动复位方式创建的事件对象,在其置位后一被WaitForSingleObject()等待到就

19、会立即复位,也就是说在执行ThreadProc12()中的受保护代码时,事件对象已经是复位状态的,这时即使有ThreadProc13()对CPU的抢占,也会由于WaitForSingleObject()没有hEvent的置位而不能继续执行,也就没有可能破坏受保护的共享资源。在ThreadProc12()中的处理完成后可以通过SetEvent()对hEvent的置位而允许ThreadProc13()对共享资源g_cArray的处理。这里SetEvent()所起的作用可以看作是对某项特定任务完成的通知。使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,其前提是得到

20、对此事件对象的访问权。可以通过OpenEvent()函数获取得到,其函数原型为:HANDLEOpenEvent(DWORDdwDesiredAccess,/访问标志BOOLbInheritHandle,/继承标志LPCTSTRlpName/指向事件对象名的指针);如果事件对象已创建(在创建事件时需要指定事件名),函数将返回指定事件的句柄。对于那些在创建事件时没有指定事件名的事件内核对象,可以通过使用内核对象的继承性或是调用DuplicateHandle()函数来调用CreateEvent()以获得对指定事件对象的访问权。在获取到访问权后所进行的同步操作与在同一个进程中所进行的线程同步操作是一样

21、的。如果需要在一个线程中等待多个事件,则用WaitForMultipleObjects()来等待。WaitForMultipleObjects()与WaitForSingleObject()类似,同时监视位于句柄数组中的所有句柄。这些被监视对象的句柄享有平等的优先权,任何一个句柄都不可能比其他句柄具有更高的优先权。WaitForMultipleObjects()的函数原型为:DWORDWaitForMultipleObjects(DWORDnCount,/等待句柄数CONSTHANDLE*lpHandles,/句柄数组首地址BOOLfWaitAll,/等待标志DWORDdwMillisecon

22、ds/等待时间间隔);参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值,则说明所有指定对象的状态均为已通知状态(当fWaitAll为T

23、RUE时)或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引(当fWaitAll为FALSE时)。如果返回值在WAIT_ABANDONED_0与WAIT_ABANDONED_0+nCount-1之间,则表示所有指定对象的状态均为已通知,且其中至少有一个对象是被丢弃的互斥对象(当fWaitAll为TRUE时),或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引(当fWaitAll为FALSE时)。 下面给出的代码主要展示了对WaitForMultipleObjects()函数的使用。通过对两个事件内核对象的等待来控制线程任务的执行与中途退出:代码 HANDL

24、EhEvents2; /存放事件句柄的数组UINTThreadProc14(LPVOIDpParam)DWORDdwRet1=WaitForMultipleObjects(2,hEvents,FALSE,INFINITE);/等待开启事件if(dwRet1=WAIT_OBJECT_0)/如果开启事件到达则线程开始执行任务AfxMessageBox(线程开始工作!);while(true)for(inti=0;i10000;i+);DWORDdwRet2=WaitForMultipleObjects(2,hEvents,FALSE,0);/在任务处理过程中等待结束事件if(dwRet2=WAIT

25、_OBJECT_0+1)/如果结束事件置位则立即终止任务的执行break;AfxMessageBox(线程退出!);return0;voidCSample08View:OnStartEvent()for(inti=0;i2;i+)/创建线程hEventsi=CreateEvent(NULL,FALSE,FALSE,NULL);AfxBeginThread(ThreadProc14,NULL); /开启线程SetEvent(hEvents0); /设置事件0(开启事件)voidCSample08View:OnEndevent()SetEvent(hEvents1);/设置事件1(结束事件)MFC

26、为事件相关处理也提供了一个CEvent类,共包含有除构造函数外的4个成员函数PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分别相当与Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函数。而构造函数则履行了原CreateEvent()函数创建事件对象的职责,其函数原型为:CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATT

27、RIBUTES lpsaAttribute = NULL );3.信号量内核对象信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1