用MFC实现多线程应用程序

多线程应用程序
 
   当使分开的任务并发执行能够带来性能的提升时,你可以在你的应用程序中使用多线程。例如:考虑一个文字处理软件,它每5分钟备份一次当前的文档。用户经应用程序主窗口到文档的输入由主线程处理。应用程序代码可以创建一个分开的线程来负责安排和执行自动备份。建立一个辅线程可以防止对较长文档的备份工作影响用户界面的响应能力。
 
   使用多线程可以为你的应用程序带来性能上的增益的情况包括:
 
   ■ 排班(时间驱动)的活动 文字处理软件例子中执行自动备份特性的线程在5分钟间隔内被阻塞。在Win32应用程序中,线程排班可以被设置到毫秒精度。
 
   ■ 事件驱动的活动 线程可以被来自其它线程的信号触发。举一个监视系统的例子:记录错误的线程通常处于非活动状态,直到其它线程通知它某种错误发生时才活动。
 
   ■ 分布式的活动 当数据必须从多个计算机收集(或派发给多个计算机)时,倾向于为每个请求创建一个线程以便它们可以并行处理,且处于它们自己的时间框架内。
 
   ■ 区分优先次序的活动 Win32线程可以被赋予一个优先级来决定由线程排班程序分配给它的运行时间的比例。有时,为了提高程序的响应能力,将它所要做的工作分为一个高优先级的线程来处理用户界面和一个低优先级的线程来处理后台工作将会很有用。
 
 MFC的多线程:CWinThread类
 
   在MFC中所有的线程都由CWinThread对象来表现,包括你的应用程序的主线程。主线程由一个起源于CWinApp的类实现,而CWinApp直接起源于CWinThread。
 
   虽然Win32 API提供了_beginthreadex函数,可以让你在底层启动线程,但是,你应该总是使用CWinThread类来创建那些需要使用MFC功能的线程。这是因为CWinThread类使用线程本地存储来管理在MFC环境中的线程的上下文信息。你可以直接声明CWinThread对象,但在许多情况下,你将让MFC全局函数AfxBeginThread()来为你创建一个CWinThread对象。
 
   CWinThread::CreateThread()函数用来启动新的线程。CWinThread类也提供了SuspendThread()和ResumeThread()函数以便你挂起和恢复线程的执行。
 
 
 工作线程和用户界面线程
 
   MFC区别两种线程:工作线程和用户界面线程。这种区分是由MFC自己进行的,Win32 API不区分线程的种类。
 
   工作线程一般用来完成那些不需要用户输入的后台任务。可举的例子包括数据库备份功能和网络联接状态监视功能。
 
   用户界面线程能够处理用户输入,它们通过实现消息循环来响应那些由用户与应用程序交互所产生的事件和消息。用户界面线程的最好的例子是你的应用程序中由源自CWinApp的类所表现的主线程。辅助的用户界面线程可以提供一种方法,使得与应用程序的交互不会降低其它应用特性的性能。例如,考虑一个可以让麻醉师监视手术中的病人情况的应用程序。一个用户界面线程可以使麻醉师进入他管理的麻药的细节,而不至于打断用于监视病人生命状况的线程。
 
   你通过在MFC应用程序中调用全局函数AfxBeginThread()来创建辅线程。AfxBeginThread()有两种重载的形式,一种用来创建工作线程,另一种用来创建用户界面线程。下面的部分将演示如何使用这两种形式。
 
 
 建立工作线程
 
   建立一个工作线程只是简单的实现一个控制函数并将其地址传给适当的形式的AfxBeginThread()函数的问题,这个控制函数执行你的线程所要执行的内容。
 
   控制函数应具有下面的语法格式:
 
   UINT MyControllingFunction(LPVOID pParam);
 
   其中的参数是一个单一的32位值,它可以用于许多用途,也可以被忽略。它可以给函数传递一个简单的值,也可以传递一个含有多个参数的结构的指针。如果该参数设计一个结构,那么这个结构不仅能用来从调用者向线程传送数据,也可以用来将数据从线程传送会调用者。
 
   工作线程形式的AfxBeginThread()是这样声明的:
 
   CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
      LPVOID pParam,
      int nPriority = THREAD_PRIORITY_NORMAL,
      UINT nStackSize = 0,
      DWORD dwCreateFlags = 0,
      LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);
 
   前两个参数是控制函数的地址和要传送给控制函数的参数。其余的参数(它们都有默认值)可以让你指定线程的优先级、栈大小、创建后是立即挂起还是立即运行。最后的参数允许你指定线程的安全属性--默认值NULL表示该线程将继承调用线程的安全属性。
 
   AfxBeginThread()创建一个新的CWinThread类,调用其CreateThread()函数来启动新线程,并返回该线程的指针。贯穿整个过程的检查保证如果创建中的任何一步失败,所有对象都将被正确释放。为了结束该线程,你可以在该线程中调用全局函数AfxEndThread()或只是简单的从控制函数返回。控制函数的返回值一般被用来指示终止的原因。传统上,如果函数成功,退出代码为0。非零值用来指示指定类型的错误。
 
 建立用户界面线程
 
   正如我们前面所提到的,MFC应用程序中的所有线程都由CWinThread类表现,这个类由AfxBeginThread()函数创建。当你创建一个工作线程时,AfxBeginThread()为你创建一个一般的CWinThread对象,并将你的控制函数的地址赋给CWinThread::m_pfnThreadProc成员变量。为了建立一个用户界面线程,你必从CWinThread引出你自己的类,并将该类的运行时信息传给用户界面形式的AfxBeginThread()。
 
   下面摘自MFC源代码的片断展示了框架如何区分工作线程和用户界面线程。代码摘自_AfxThreadEntry(),所有MFC线程的入口点。
 
     // First -- check for simple worker thread
     DWORD nResult = 0;
     if (pThread->m_pfnThreadProc != NULL)
     {
      nResult = (*pThread->m_pfnThreadProc)(pThread->m_pThreadParams);
      ASSERT_VALID(pThread);
     }
     // Else -- check for thread with message loop
     else if (!pThread->InitInstance())
     {
      ASSERT_VALID(pThread);
      nResult = pThread->ExitInstance();
     }
     else
     {
      // will stop after PostQuitMessage called
      ASSERT_VALID(pThread);
      nResult = pThread->Run();
     }
     // Clean up and shut down the thread
     threadWnd.Detach();
     AfxEndThread(nResult);
 
   如果m_pfnThreadProc指向一个控制线程,代码便知道它正在处理一个工作线程。控制线程被调用后线程被终止。如果m_pfnThreadProc为NULL,该函数假定它正在处理一个用户界面线程。于是调用该线程对象的InitInstance()函数进行线程的初始化--例如,建立主窗口和其它的用户界面对象。如果InitInstance()成功返回(即返回TRUE),Run()函数被调用。CWinThread::Run()实现一个消息循环来处理有该线程主窗口的消息。
 
   InitInstance()和Run()函数你应该听起来很熟悉。它们是CWinApp继承的两个关键的虚函数。
 
   你被责成为你的线程类提供一个重载的InitInstance()。你经常要提供一个针对一个类的重载的CWinThread::ExitInstance()作为清除函数。通常你将使用基类的Run()函数。
 
 线程同步
 
   辅线程通常用来实现异步处理。一个异步操作是一个不依赖于其它事件或活动的执行。考虑我们的定时器线程,它运行在自己的执行路径上,每秒检查一次系统时钟。它不需要等待应用程序主线程中的事件,并且应用程序可以正常进行而不必等待定时器线程完成它的任务。
 
   很多情况下这些异步的活动需要同步,或协调它们的操作。例如,考虑一个排队有其它应用程序创建的打印任务线程的打印排班线程。打印任务线程会需要通知调度程序它想加入队列,并且调度程序要给队列中轮到打印的的任务发送消息。
 
   另一种典型的需要同步的假设是对应用程序全局数据的更新。考虑一个应用程序,它拥有一个传感器线程,该线程从传感器设备读取数据来更新一个数据结构。另一个线程用来读取该结构并在屏幕上显示其状态图像。现在假设显示线程尝试读取一份数据,而恰恰在这一毫秒,该数据结构正在被传感器线程更新。其结果很可能是被破坏的数据,这将可能导致严重的后果,例如,如果这个应用程序是一个监视核电站的程序。线程对全局数据的访问必须被同步,以保证在任何时刻只有一个线程能够读取或修改该数据。
 
   一种实现线程间同步的方法是使用一个全局对象来充当线程间的中间媒体。MFC提供了一组同步类,在表中列出。这些源自CSyncObject的同步类可以被用来协调任何种类的异步事件。
 
   表 MFC同步类
   ──────────────────────────────────────
   名称 描述
   ──────────────────────────────────────
   CCriticalSection 只允许当前进程中的一个线程访问某个对象的同步类
   CMutes 只允许系统中一个进程内的一个线程访问某个对象的同步类
   CSymaphore 只允许一到某个指定数目个线程同时访问某个对象的同步类
   CEvent 当某个事件发生时通知一个应用程序的同步类
   ──────────────────────────────────────
 
   这些同步类与同步访问类:CSingleLock和CMultiLock联合使用来提供对全局数据或共享资源的线程安全访问。对这些类的建议的用法如下:
 
   ■ 将对访问全局数据或资源的函数封装到一个类中。通过使用公有函数来保护受控制的数据和受管制的访问。
 
   ■ 在你的类中,建立一个适当类型的同步对象。例如,你要使用一个CCriticalSection对象来保证在某个时刻只有一个线程更新你的全局数据;或者,你可能包含一个CEvent对象来表示某个资源已经准备好接受数据。
 
   ■ 在提供对数据或资源的访问的函数中,创建一个同步访问对象的实例。当你一次需要等待一个对象时使用CSingleLock;当在某个特殊时刻你可以使用多个对象时使用CMultiLock。
 
   ■ 在函数代码试图访问受保护的数据之前,调用同步访问对象的Lock()成员函数。Lock()函数可以被指明在指定长(或不确定)的时间内等待相关联的对象变得可用。例如,一个CCriticalSection对象在为当前线程获得安全的排外的访问权时将变得可用。一个事件的可用性由程序代码调用CEvent::SetEvent()函数来设置。
 
   ■ 当函数完成了对受保护数据的访问后,调用访问对象的Unlock()函数或者让该对象销毁。
 

   将全局资源和同步代码封装到一个线程安全的类中,可以帮助你集中和管理对资源的访问,并且防止死锁的发生。死锁是一种当两个或更多的线程都等待其间其它成员释放某个共享资源以便运行时的情况。死锁条件是有名的难以重现和跟踪的,因此,你被强烈的建议去分析你的多线程应用程序来发现可能的死锁条件,并采取相应的步骤来防止它们发生。

出自:http://88442.blog.163.com/blog/static/5376990200872534144250/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值