MFC多线程编程总结

时间:2021-11-12 18:31:21

        在MFC程序中使用AfxBeginThread函数来创建一个线程,该函数因参数不同而具有两种重载函数,分别对应工作者线程和用户接口(UI)线程。

一、工作线程

1、创建线程MFC API函数

       CWinThread *AfxBeginThread(
                     AFX_THREADPROC pfnThreadProc, //线程函数
                     LPVOID pParam, //传递给控制函数的参数
                     int nPriority = THREAD_PRIORITY_NORMAL, //线程的优先级
                     UINT nStackSize = 0, //线程的堆栈大小
                     DWORD dwCreateFlags = 0, //线程的创建标志
                     LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //线程的安全属性
                     );

2、线程工作函数

 UINT MfcThreadProc(LPVOID lpParam)
 {
  // 对指针进行强制转型,对指针所指内容进行解码。
    CExampleClass *lpObject = (CExampleClass*)lpParam;
  if (lpObject == NULL || !lpObject->IsKindof(RUNTIME_CLASS(CExampleClass)))
   return - 1; //输入参数非法 
  //线程成功启动
  while (1)
  {
     ..//
  }
  return 0; //返回0则表示线程正确退出线程
 }

3、使用例

        m_pThread= AfxBeginThread(NewThreadTest,&wenli);
        m_pThread ->m_bAutoDelete = TRUE;
        m_pThread ->ResumeThread();


          m_bAutoDelete= TRUE;

          系统自己清理CWind对象,当然还包括CloseHandle(),ExitInstance()等等一堆函数的调用。

          m_bAutoDelete = FALSE; 

          自己在用完后调用delete()删除创建线程的对象,否则会有内存泄漏问题。

二、UI线程

      UI 线程 是由CWinThread 派生类控制的,这个派生类和CWinApp 极为类似,实际上CWinApp 也是一个UI线程,他是应用程序的主线程 ,一般我们所说的UI 线程,是指除主线程 之外的界面 线程。

     1,创建用户界面线程时,必须首先从CWinThread派生类。

     2,使用DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREATE以建立类表。

     3,使用VS提供的增加MFC类向导可以轻松完成类的添加工作,类添加后需要对其进行必要的修改。

         需要重写如下两个成员函数。
              //执行线程实例初始化,必须重写
         virtualBOOL InitInstance();

             //线程终止时执行清除,通常需要重写
        virtualint ExitInstance(); // default will 'delete this'

     4,创建线程函数原型。

             CWinThread *AfxBeginThread(
                                   CRuntimeClass *pThreadClass, //从CWinThread派生的类的 RUNTIME_CLASS
                                    int nPriority =THREAD_PRIORITY_NORMAL,
                                   UINT nStackSize = 0,
                                   DWORD dwCreateFlags = 0,
                                   LPSECURITY_ATTRIBUTES lpSecurityAttrs= NULL);



      5,启动线程

            设置CWinThread 类的m_pMainWnd成员,否则这个线程不会随着界面的关闭而退出。

            CWinThread *pTread = AfxBeginThread(RUNTIME_CLASS(CmyUIClassName));

       6,结束线程

       ①UI线程有消息队列,所以结束一个UI线程最好的方法是发一个WM_QUIT消息给消息队列。

             PostQuitMessage()

             PostThreadMessage()

             但是发出消息后最好等待看UI线程是否已经退出(很重要)。

           if(pThread)   
           {  
                // 1. 发一个WM_QUIT 消息结 UI 线程  
                 pThread->PostThreadMessage(WM_QUIT, NULL, NULL);  
               // 2. 等待 UI 线程正常退出  
              if (WAIT_OBJECT_0 == WaitForSingleObject(pThread->m_hThread, INFINITE))  
              {  
                  // 3. 删除 UI 线程对象,只有当你设置了m_bAutoDelete = FALSE; 时才调用  
               delete   pThread;   
              } 
          }  

   ②普通的工作线程收到返回值既表示正确退出。

三、线程间的通信

    1、全局变量方式。

    2、控件指针或其它全局参数传递方式。

         AfxBeginThread(NewThreadTest,&wenli);

    3,消息传递方式

       A、工作线程可以使用PostThreadMessage()

           消息映射为ON_THREAD_MESSAGE而不是ON_MESSAGE

       B、UI线程可以使用

         异步函数:PostMessage()

        同步函数:SendMessage()

       C、使用方法

             #define WM_THREADMSG  WMUSER+100

              全局函数::PostThreadMessage(idThread,WM_THREADMSG,parm1, parm2);

             或者使用类成员函数

              m_pThread->PostThreadMessage(WM_THREADMSG,parm1, parm2); 


       参考博客:http://blog.csdn.net/qq61394323/article/details/25334293

 

四、线程同步

      各个线程可以访问进程中的公共变量,资源,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。数据之间的相互制约包括

       1、直接制约关系,即一个线程的处理结果,为另一个线程的输入,因此线程之间直接制约着,这种关系可以称之为同步关系
   2、间接制约关系,即两个线程需要访问同一资源,该资源在同一时刻只能被一个线程访问,这种关系称之为线程间对资源的互斥访问,某种意义上说互斥是一种制约关系更小的同步。

A、 临界区(CCriticalSection)

       当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止。具体应用方式:

       1、 定义临界区对象CcriticalSection g_CriticalSection;

       2、 在访问共享资源(代码或变量)之前,先获得临界区对象,g_CriticalSection.Lock();

       3、 访问共享资源后,则放弃临界区对象,g_CriticalSection.Unlock();

 

B、 事件(CEvent)

       事件机制,则允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。比如在某些网络应用程序中,一个线程如A负责侦听通信端口,另外一个线程B负责更新用户数据,利用事件机制,则线程A可以通知线程B何时更新用户数据。每个Cevent对象可以有两种状态:有信号状态和无信号状态。Cevent类对象有两种类型:人工事件和自动事件。

       自动事件对象,在被至少一个线程释放后自动返回到无信号状态;

       人工事件对象,获得信号后,释放可利用线程,但直到调用成员函数ReSet()才将其设置为无信号状态。在创建Cevent对象时,默认创建的是自动事件。

      1、CEvent(BOOLbInitiallyOwn=FALSE,
                     BOOLbManualReset=FALSE,
                     LPCTSTRlpszName=NULL,
                     LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);

                            bInitiallyOwn:指定事件对象初始化状态,TRUE为有信号,FALSE为无信号;
                            bManualReset:指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件,FALSE为自动事件;
后两个参数一般设为NULL,在此不作过多说明。
       2、BOOLCEvent::SetEvent();
        将CEvent 类对象的状态设置为有信号状态。如果事件是人工事件,则CEvent 类对象保持为有信号状态,直到调用成员函数ResetEvent()将 其重新设为无信号状态时为止。如果CEvent 类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent 类对象由系统自动重置为无信号状态。
如果该函数执行成功,则返回非零值,否则返回零。
       

        3、BOOLCEvent::ResetEvent();
  该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。如果该函数执行成功,返回非零值,否则返回零。我们一般通过调用WaitForSingleObject函数来监视事件状态。  



C、 互斥量(CMutex)

       互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用。当然,互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。

D、 信号量(CSemphore)

         当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。CSemaphore 类的对象保存了对当前访问某一指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程的数目。如果这个计数达到了零,则所有对这个CSemaphore 类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零时为止。