1. 程式人生 > >MFC多執行緒程式設計總結

MFC多執行緒程式設計總結

        在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 類物件所控制的資源的訪問嘗試都被放入到一個佇列中等待,直到超時或計數值不為零時為止。