用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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章