天天看點

VC++多線程程式設計一、問題的提出二、多線程概述三、Win32 API對多線程程式設計的支援四、 Win32 API多線程程式設計例程

編寫一個耗時的單線程程式:

  建立一個基于對話框的應用程式SingleThread,在主對話框IDD_SINGLETHREAD_DIALOG添加一個按鈕,ID為IDC_SLEEP_SIX_SECOND,标題為“延時6秒”,添加按鈕的響應函數,代碼如下:

  編譯并運作應用程式,單擊“延時6秒”按鈕,你就會發現在這6秒期間程式就象“當機”一樣,不在響應其它消息。為了更好地處理這種耗時的操作,我們有必要學習——多線程程式設計。

先來看一個工作線程的例子

  程序和線程都是作業系統的概念。程序是應用程式的執行執行個體,每個程序是由私有的虛拟位址空間、代碼、資料和其它各種系統資源組成,程序在運作過程中建立的資源随着程序的終止而被銷毀,所使用的系統資源在程序終止時被釋放或關閉。

線程是程序内部的一個執行單元。系統建立好程序後,實際上就啟動執行了該程序的主執行線程,主執行線程以函數位址形式,比如說main或WinMain函數,将程式的啟動點提供給Windows系統。主執行線程終止了,程序也就随之終止。

每一個程序至少有一個主執行線程,它無需由使用者去主動建立,是由系統自動建立的。使用者根據需要在應用程式中建立其它線程,多個線程并發地運作于同一個程序中。一個程序中的所有線程都在該程序的虛拟位址空間中,共同使用這些虛拟位址空間、全局變量和系統資源,是以線程間的通訊非常友善,多線程技術的應用也較為廣泛。

多線程可以實作并行處理,避免了某項任務長時間占用CPU時間。要說明的一點是,目前大多數的計算機都是單處理器(CPU)的,為了運作所有這些線程,作業系統為每個獨立線程安排一些CPU時間,作業系統以輪換方式向線程提供時間片,這就給人一種假象,好象這些線程都在同時運作。由此可見,如果兩個非常活躍的線程為了搶奪對CPU的控制權,線上程切換時會消耗很多的CPU資源,反而會降低系統的性能。這一點在多線程程式設計時應該注意。

Win32 SDK函數支援進行多線程的程式設計,并提供了作業系統原理中的各種同步、互斥和臨界區等操作。Visual C++ 6.0中,使用MFC類庫也實作了多線程的程式設計,使得多線程程式設計更加友善。

  Win32 提供了一系列的API函數來完成線程的建立、挂起、恢複、終結以及通信等工作。下面将選取其中的一些重要函數進行說明。

該函數在其調用程序的程序空間裡建立一個新的線程,并傳回已建線程的句柄,其中各參數說明如下:

lpThreadAttributes:指向一個 SECURITY_ATTRIBUTES 結構的指針,該結構決定了線程的安全屬性,一般置為 NULL;

dwStackSize:指定了線程的堆棧深度,一般都設定為0;

lpStartAddress:表示新線程開始執行時代碼所在函數的位址,即線程的起始位址。一般情況為(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是線程函數名;

lpParameter:指定了線程執行時傳送給線程的32位參數,即線程函數的參數;

dwCreationFlags:控制線程建立的附加标志,可以取兩種值。如果該參數為0,線程在被建立後就會立即開始執行;如果該參數為CREATE_SUSPENDED,則系統産生線程後,該線程處于挂起狀态,并不馬上執行,直至函數ResumeThread被調用;

lpThreadId:該參數傳回所建立線程的ID;

如果建立成功則傳回線程的句柄,否則傳回NULL。

該函數用于挂起指定的線程,如果函數執行成功,則線程的執行被終止。

該函數用于結束線程的挂起狀态,執行線程。

該函數用于線程正常終結自身的執行(也可以用AfxEndThread(隻能線上程内部), PostQuitMessage來正常終止一個線程),主要線上程的執行函數中被調用。其中參數dwExitCode用來設定線程的退出碼。

  一般情況下,線程運作結束之後,線程函數正常傳回,但是應用程式可以調用TerminateThread強行終止(異常終止一個線程)某一線程的執行。各參數含義如下:

hThread:将被終結的線程的句柄;

dwExitCode:用于指定線程的退出碼。

  使用TerminateThread()終止某個線程的執行是不安全的,可能會引起系統不穩定;雖然該函數立即終止線程的執行,但并不釋放線程所占用的資源。是以,一般不建議使用該函數。

該函數将一條消息放入到指定線程的消息隊列中,并且不等到消息被該線程處理時便傳回。

idThread:将接收消息的線程的ID;

Msg:指定用來發送的消息;

wParam:同消息有關的字參數;

lParam:同消息有關的長參數;

調用該函數時,如果即将接收消息的線程沒有建立消息循環,則該函數執行失敗。

     7、BOOL GetExitCodeThread( HANDLE hTread, LPDWORD lpExitCode)擷取線程的退出碼。

hTread可在CWinThread的成員變量m_hTread獲得。如果線程還沒有終止,則獲得STILL_ACTIVE(0x103)。

     8、GetThreadPriority  SetThreadPriority GetTPriorityClass  SetPriorityClass (後兩個針對CWinThread) 線程優先級

例程1 MultiThread1

建立一個基于對話框的工程MultiThread1,在對話框IDD_MULTITHREAD1_DIALOG中加入兩個按鈕和一個編輯框,兩個按鈕的ID分别是IDC_START,IDC_STOP ,标題分别為“啟動”,“停止”,IDC_STOP的屬性選中Disabled;編輯框的ID為IDC_TIME ,屬性選中Read-only;

在MultiThread1Dlg.h檔案中添加線程函數聲明:

注意,線程函數的聲明應在類CMultiThread1Dlg的外部。 在類CMultiThread1Dlg内部添加protected型變量:

分别代表線程的句柄和ID。

在MultiThread1Dlg.cpp檔案中添加全局變量m_bRun :

m_bRun 代表線程是否正在運作。

你要留意到全局變量 m_bRun 是使用 volatile 修飾符的,volatile 修飾符的作用是告訴編譯器無需對該變量作任何的優化,即無需将它放到一個寄存器中,并且該值可被外部改變。對于多線程引用的全局變量來說,volatile 是一個非常重要的修飾符。

編寫線程函數:

該線程函數沒有參數,也不傳回函數值。隻要m_bRun為TRUE,線程一直運作。

輕按兩下IDC_START按鈕,完成該按鈕的消息函數:

輕按兩下IDC_STOP按鈕,完成該按鈕的消息函數:

編譯并運作該例程,體會使用Win32 API編寫的多線程。

例程2 MultiThread2

  該線程示範了如何傳送一個一個整型的參數到一個線程中,以及如何等待一個線程完成處理。

建立一個基于對話框的工程MultiThread2,在對話框IDD_MULTITHREAD2_DIALOG中加入一個編輯框和一個按鈕,ID分别是IDC_COUNT,IDC_START ,按鈕控件的标題為“開始”;

在MultiThread2Dlg.h檔案中添加線程函數聲明:

注意,線程函數的聲明應在類CMultiThread2Dlg的外部。

在類CMultiThread2Dlg内部添加protected型變量:

打開ClassWizard,為編輯框IDC_COUNT添加int型變量m_nCount。 在MultiThread2Dlg.cpp檔案中添加:

順便說一下WaitForSingleObject函數,其函數原型為:

hHandle為要監視的對象(一般為同步對象,也可以是線程)的句柄;

dwMilliseconds為hHandle對象所設定的逾時值,機關為毫秒;

  當在某一線程中調用該函數時,線程暫時挂起,系統監視hHandle所指向的對象的狀态。如果在挂起的dwMilliseconds毫秒内,線程所等待的對象變為有信号狀态,則該函數立即傳回;如果逾時時間已經到達dwMilliseconds毫秒,但hHandle所指向的對象還沒有變成有信号狀态,函數照樣傳回。參數dwMilliseconds有兩個具有特殊意義的值:0和INFINITE。若為0,則該函數立即傳回;若為INFINITE,則線程一直被挂起,直到hHandle所指向的對象變為有信号狀态時為止。

  本例程調用該函數的作用是按下IDC_START按鈕後,一直等到線程傳回,再恢複IDC_START按鈕正常狀态。 編譯運作該例程并細心體會。

例程3 MultiThread3

傳送一個結構體給一個線程函數也是可能的,可以通過傳送一個指向結構體的指針參數來完成。 先定義一個結構體:

建立線程時

在threadFunc函數内部,可以使用“強制轉換”:

例程3 MultiThread3将示範如何傳送一個指向結構體的指針參數。

建立一個基于對話框的工程MultiThread3,在對話框IDD_MULTITHREAD3_DIALOG中加入一個編輯框IDC_MILLISECOND,一個按鈕IDC_START,标題為“開始” ,一個進度條IDC_PROGRESS1;

打開ClassWizard,為編輯框IDC_MILLISECOND添加int型變量m_nMilliSecond,為進度條IDC_PROGRESS1添加CProgressCtrl型變量m_ctrlProgress;

在MultiThread3Dlg.h檔案中添加一個結構的定義:

線程函數的聲明:

注意,二者應在類CMultiThread3Dlg的外部。

在類CMultiThread3Dlg内部添加protected型變量:

在MultiThread3Dlg.cpp檔案中進行如下操作:

定義公共變量 threadInfo Info;

輕按兩下按鈕IDC_START,添加相應消息處理函數:

在函數BOOL CMultiThread3Dlg::OnInitDialog()中添加語句:

添加線程處理函數:UINT ThreadFunc(LPVOID lpParam)

VC++多線程程式設計一、問題的提出二、多線程概述三、Win32 API對多線程程式設計的支援四、 Win32 API多線程程式設計例程

  順便補充一點,如果你在void CMultiThread3Dlg::OnStart() 函數中添加/* */語句,編譯運作你就會發現進度條不進行重新整理,主線程也停止了反應。什麼原因呢? 這是因為WaitForSingleObject函數等待子線程(ThreadFunc)結束時,導緻了線程死鎖。因為WaitForSingleObject函數會将主線程挂起(任何消息都得不到處理),而子線程ThreadFunc正在設定進度條,一直在等待主線程将重新整理消息處理完畢傳回才會檢測通知事件。這樣兩個線程都在互相等待,死鎖發生了,程式設計時應注意避免。

例程4 MultiThread4

該例程測試在Windows下最多可建立線程的數目。

建立一個基于對話框的工程MultiThread4,在對話框IDD_MULTITHREAD4_DIALOG中加入一個按鈕IDC_TEST和一個編輯框IDC_COUNT,按鈕标題為“測試” , 編輯框屬性選中Read-only;

在MultiThread4Dlg.cpp檔案中進行如下操作:

添加公共變量

該變量表示是否還能繼續建立線程。

添加線程函數:

隻要 m_bRunFlag 變量為TRUE,線程一直運作。

輕按兩下按鈕IDC_TEST,添加其響應消息函數:

  MFC中有兩類線程,分别稱之為工作者線程和使用者界面線程。二者的主要差別在于工作者線程沒有消息循環,而使用者界面線程有自己的消息隊列和消息循環。

工作者線程沒有消息機制,通常用來執行背景計算和維護任務,如冗長的計算過程,列印機的背景列印等。使用者界面線程一般用于處理獨立于其他線程執行之外的使用者輸入,響應使用者及系統所産生的事件和消息等。但對于Win32的API程式設計而言,這兩種線程是沒有差別的,它們都隻需線程的啟動位址即可啟動線程來執行任務。

在MFC中,一般用全局函數AfxBeginThread()來建立并初始化一個線程的運作,該函數有兩種重載形式,分别用于建立工作者線程和使用者界面線程。兩種重載函數原型和參數分别說明如下:

PfnThreadProc:指向工作者線程的執行函數的指針,線程函數原型必須聲明如下:

請注意,ExecutingFunction()應傳回一個UINT類型的值,用以指明該函數結束的原因。一般情況下,傳回0表明執行成功。

pParam:傳遞給線程函數的一個32位參數,執行函數将用某種方式解釋該值。它可以是數值,或是指向一個結構的指針,甚至可以被忽略;

nPriority:線程的優先級。如果為0,則線程與其父線程具有相同的優先級;

nStackSize:線程為自己配置設定堆棧的大小,其機關為位元組。如果nStackSize被設為0,則線程的堆棧被設定成與父線程堆棧相同大小;

dwCreateFlags:如果為0,則線程在建立後立刻開始執行。如果為CREATE_SUSPEND,則線程在建立後立刻被挂起;

lpSecurityAttrs:線程的安全屬性指針,一般為NULL;

  pThreadClass 是指向 CWinThread 的一個導出類的運作時類對象的指針,該導出類定義了被建立的使用者界面線程的啟動、退出等;其它參數的意義同形式1。使用函數的這個原型生成的線程也有消息機制,在以後的例子中我們将發現同主線程的機制幾乎一樣。

下面我們對CWinThread類的資料成員及常用函數進行簡要說明。

m_hThread:目前線程的句柄;

m_nThreadID:目前線程的ID;

m_pMainWnd:指向應用程式主視窗的指針

  該函數中的dwCreateFlags、nStackSize、lpSecurityAttrs參數和API函數CreateThread中的對應參數有相同含義,該函數執行成功,傳回非0值,否則傳回0。

一般情況下,調用AfxBeginThread()來一次性地建立并啟動一個線程,但是也可以通過兩步法來建立線程:首先建立CWinThread類的一個對象,然後調用該對象的成員函數CreateThread()來啟動該線程。

   重載該函數以控制使用者界面線程執行個體的初始化。初始化成功則傳回非0值,否則傳回0。使用者界面線程經常重載該函數,工作者線程一般不使用InitInstance()。

   線上程終結前重載該函數進行一些必要的清理工作。該函數傳回線程的退出碼,0表示執行成功,非0值用來辨別各種錯誤。同InitInstance()成員函數一樣,該函數也隻适用于使用者界面線程。

  在Visual C++ 6.0程式設計環境中,我們既可以編寫C風格的32位Win32應用程式,也可以利用MFC類庫編寫C++風格的應用程式,二者各有其優缺點。基于Win32的應用程式執行代碼小巧,運作效率高,但要求程式員編寫的代碼較多,且需要管理系統提供給程式的所有資源;而基于MFC類庫的應用程式可以快速建立起應用程式,類庫為程式員提供了大量的封裝類,而且Developer Studio為程式員提供了一些工具來管理使用者源程式,其缺點是類庫代碼很龐大。由于使用類庫所帶來的快速、簡捷和功能強大等優越性,是以除非有特殊的需要,否則Visual C++推薦使用MFC類庫進行程式開發。

我們知道,MFC中的線程分為兩種:使用者界面線程和工作者線程。我們将分别舉例說明。

例程5 MultiThread5

為了與Win32 API對照,我們使用MFC 類庫程式設計實作例程3 MultiThread3。

建立一個基于對話框的工程MultiThread5,在對話框IDD_MULTITHREAD5_DIALOG中加入一個編輯框IDC_MILLISECOND,一個按鈕IDC_START,标題為“開始” ,一個進度條IDC_PROGRESS1;

在MultiThread5Dlg.h檔案中添加一個結構的定義:

注意,二者應在類CMultiThread5Dlg的外部。

在類CMultiThread5Dlg内部添加protected型變量:

在MultiThread5Dlg.cpp檔案中進行如下操作: 定義公共變量:

添加線程處理函數:

使用者界面線程是從CWinThread類派生來的,是以在實作使用者界面線程的時候要從CWinThread類派生出新類,并重寫派生類的成員函數。一般情況下隻需重寫InitInstance(初始化)和ExitInstance(線程終止的清理)這個函數。可以借助ClassWizard來簡化程式的編寫。

使用者界面線程的啟動:

1. 采用面向對象的方式

2. 調用AfxBeginThread函數

  pThreadClass 是指向 CWinThread 的一個導出類的運作時類對象的指針,該導出類定義了被建立的使用者界面線程的啟動、退出等。

CWinThread類其他說明:其成員變量m_pMainWnd指向主視窗,一般在InitInstance中指定;m_pActiveWnd指向激活視窗;可以使用GetMainWnd來擷取線程主視窗。

建立使用者界面線程的步驟:

使用ClassWizard建立類CWinThread的派生類(以CUIThread類為例)

重載函數InitInstance()和ExitInstance()。

建立新的使用者界面線程

請注意以下兩點:

A、在UIThreadDlg.cpp的開頭加入語句:

B、把UIThread.h中類CUIThread()的構造函數的特性由 protected 改為 public。

  使用者界面線程的執行次序與應用程式主線程相同,首先調用使用者界面線程類的InitInstance()函數,如果傳回TRUE,繼續調用線程的Run()函數,該函數的作用是運作一個标準的消息循環,并且當收到WM_QUIT消息後中斷,在消息循環過程中,Run()函數檢測到線程空閑時(沒有消息),也将調用OnIdle()函數,最後Run()函數傳回,MFC調用ExitInstance()函數清理資源。

你可以建立一個沒有界面而有消息循環的線程,例如:你可以從CWinThread派生一個新類,在InitInstance函數中完成某項任務并傳回FALSE,這表示僅執行InitInstance函數中的任務而不執行消息循環,你可以通過這種方法,完成一個工作者線程的功能。

例程6 MultiThread6

建立一個基于對話框的工程MultiThread6,在對話框IDD_MULTITHREAD6_DIALOG中加入一個按鈕IDC_UI_THREAD,标題為“使用者界面線程”

右擊工程并選中“New Class…”為工程添加基類為CWinThread派生線程類CUIThread。

給工程添加新對話框IDD_UITHREADDLG,标題為“線程對話框”。

為對話框IDD_UITHREADDLG建立一個基于CDialog的類CUIThreadDlg。使用ClassWizard為CUIThreadDlg類添加WM_LBUTTONDOWN消息的處理函數OnLButtonDown,如下:

在UIThread.h中添加

并在CUIThread類中添加protected變量CUIThread m_dlg:

分别重載InitInstance()函數和ExitInstance()函數:

輕按兩下按鈕IDC_UI_THREAD,添加消息響應函數:

并在MultiThread6Dlg.cpp的開頭添加:

  好了,編譯并運作程式吧。每單擊一次“使用者界面線程”按鈕,都會彈出一個線程對話框,在任何一個線程對話框内按下滑鼠左鍵,都會彈出一個消息框。

  一般而言,應用程式中的一個次要線程總是為主線程執行特定的任務,這樣,主線程和次要線程間必定有一個資訊傳遞的管道,也就是主線程和次要線程間要進行通信。這種線程間的通信不但是難以避免的,而且在多線程程式設計中也是複雜和頻繁的,下面将進行說明。

使用全局變量進行通信

由于屬于同一個程序的各個線程共享作業系統配置設定該程序的資源,故解決線程間通信最簡單的一種方法是使用全局變量。對于标準類型的全局變量,我們建議使用volatile 修飾符,它告訴編譯器無需對該變量作任何的優化,即無需将它放到一個寄存器中,并且該值可被外部改變。如果線程間所需傳遞的資訊較複雜,我們可以定義一個結構,通過傳遞指向該結構的指針進行傳遞資訊。

使用自定義消息

我們可以在一個線程的執行函數中向另一個線程發送自定義的消息來達到通信的目的。一個線程向另外一個線程發送消息是通過作業系統實作的。利用Windows作業系統的消息驅動機制,當一個線程發出一條消息時,作業系統首先接收到該消息,然後把該消息轉發給目标線程,接收消息的線程必須已經建立了消息循環。

例程7 MultiThread7

  該例程示範了如何使用自定義消息進行線程間通信。首先,主線程向CCalculateThread線程發送消息WM_CALCULATE,CCalculateThread線程收到消息後進行計算,再向主線程發送WM_DISPLAY消息,主線程收到該消息後顯示計算結果。

建立一個基于對話框的工程MultiThread7,在對話框IDD_MULTITHREAD7_DIALOG中加入三個單選按鈕IDC_RADIO1,IDC_RADIO2,IDC_RADIO3,标題分别為1+2+3+4+......+10,1+2+3+4+......+50,1+2+3+4+......+100。加入按鈕IDC_SUM,标題為“求和”。加入标簽框IDC_STATUS,屬性選中“邊框”;

在MultiThread7Dlg.h中定義如下變量:

代表加數的大小。

分别輕按兩下三個單選按鈕,添加消息響應函數:

并在OnInitDialog函數中完成相應的初始化工作:

在MultiThread7Dlg.h中添加:

在MultiThread7Dlg.cpp中添加:

以上代碼使得主線程類CMultiThread7Dlg可以處理WM_DISPLAY消息,即在IDC_STATUS标簽框中顯示計算結果。

輕按兩下按鈕IDC_SUM,添加消息響應函數:

OnSum()函數的作用是建立CalculateThread線程,延時給該線程發送WM_CALCULATE消息。

右擊工程并選中“New Class…”為工程添加基類為 CWinThread 派生線程類 CCalculateThread。

在檔案CalculateThread.h 中添加

在檔案CalculateThread.cpp中添加

在CalculateThread.cpp檔案的開頭添加一條:

  以上代碼為 CCalculateThread 類添加了 WM_CALCULATE 消息,消息的響應函數是 OnCalculate,其功能是根據參數 wParam 的值,進行累加,累加結果在臨時變量nTmpt中,延時0.5秒,向主線程發送WM_DISPLAY消息進行顯示,nTmpt作為參數傳遞。編譯并運作該例程,體會如何線上程間傳遞消息。

  雖然多線程能給我們帶來好處,但是也有不少問題需要解決。例如,對于像磁盤驅動器這樣獨占性系統資源,由于線程可以執行程序的任何代碼段,且線程的運作是由系統排程自動完成的,具有一定的不确定性,是以就有可能出現兩個線程同時對磁盤驅動器進行操作,進而出現操作錯誤;又例如,對于銀行系統的計算機來說,可能使用一個線程來更新其使用者資料庫,而用另外一個線程來讀取資料庫以響應儲戶的需要,極有可能讀資料庫的線程讀取的是未完全更新的資料庫,因為可能在讀的時候隻有一部分資料被更新過。

使隸屬于同一程序的各線程協調一緻地工作稱為線程的同步。MFC提供了多種同步對象:

臨界區(CCriticalSection)

事件(CEvent)

互斥量(CMutex)

信号量(CSemaphore)

通過這些類,我們可以比較容易地做到線程同步。

<b>A、使用 CCriticalSection 類</b>

  當多個線程通路一個獨占性共享資源時,可以使用“臨界區”對象。任一時刻隻有一個線程可以擁有臨界區對象,擁有臨界區的線程可以通路被保護起來的資源或代碼段,其他希望進入臨界區的線程将被挂起等待,直到擁有臨界區的線程放棄臨界區時為止,這樣就保證了不會在同一時刻出現多個線程通路共享資源。

CCriticalSection類的用法非常簡單,步驟如下:

定義CCriticalSection類的一個全局對象(以使各個線程均能通路),如CCriticalSection critical_section;

在通路需要保護的資源或代碼之前,調用CCriticalSection類的成員Lock()獲得臨界區對象:

線上程中調用該函數來使線程獲得它所請求的臨界區。如果此時沒有其它線程占有臨界區對象,則調用Lock()的線程獲得臨界區;否則,線程将被挂起,并放入到一個系統隊列中等待,直到目前擁有臨界區的線程釋放了臨界區時為止。

通路臨界區完畢後,使用CCriticalSection的成員函數Unlock()來釋放臨界區:

再通俗一點講,就是線程A執行到critical_section.Lock();語句時,如果其它線程(B)正在執行critical_section.Lock();語句後且critical_section. Unlock();語句前的語句時,線程A就會等待,直到線程B執行完critical_section. Unlock();語句,線程A才會繼續執行。

下面再通過一個執行個體進行示範說明。

例程8 MultiThread8

建立一個基于對話框的工程MultiThread8,在對話框IDD_MULTITHREAD8_DIALOG中加入兩個按鈕和兩個編輯框控件,兩個按鈕的ID分别為IDC_WRITEW和IDC_WRITED,标題分别為“寫‘W’”和“寫‘D’”;兩個編輯框的ID分别為IDC_W和IDC_D,屬性都選中Read-only;

在MultiThread8Dlg.h檔案中聲明兩個線程函數:

使用ClassWizard分别給IDC_W和IDC_D添加CEdit類變量m_ctrlW和m_ctrlD;

在MultiThread8Dlg.cpp檔案中添加如下内容:

為了檔案中能夠正确使用同步類,在檔案開頭添加:

定義臨界區和一個字元數組,為了能夠在不同線程間使用,定義為全局變量:

分别輕按兩下按鈕IDC_WRITEW和IDC_WRITED,添加其響應函數:

由于代碼較簡單,不再詳述。編譯、運作該例程,您可以連續點選兩個按鈕,觀察體會臨界類的作用。

<b>B、使用 CEvent 類</b>

  CEvent 類提供了對事件的支援。事件是一個允許一個線程在某種情況發生時,喚醒另外一個線程的同步對象。例如在某些網絡應用程式中,一個線程(記為A)負責監聽通訊端口,另外一個線程(記為B)負責更新使用者資料。通過使用CEvent 類,線程A可以通知線程B何時更新使用者資料。每一個CEvent 對象可以有兩種狀态:有信号狀态和無信号狀态。線程監視位于其中的CEvent 類對象的狀态,并在相應的時候采取相應的操作。

在MFC中,CEvent 類對象有兩種類型:人工事件和自動事件。一個自動CEvent 對象在被至少一個線程釋放後會自動傳回到無信号狀态;而人工事件對象獲得信号後,釋放可利用線程,但直到調用成員函數ReSetEvent()才将其設定為無信号狀态。在建立CEvent 類的對象時,預設建立的是自動事件。 CEvent 類的各成員函數的原型和參數說明如下:

bInitiallyOwn:指定事件對象初始化狀态,TRUE為有信号,FALSE為無信号;

bManualReset:指定要建立的事件是屬于人工事件還是自動事件。TRUE為人工事件,FALSE為自動事件;

後兩個參數一般設為NULL,在此不作過多說明。

   将 CEvent 類對象的狀态設定為有信号狀态。如果事件是人工事件,則 CEvent 類對象保持為有信号狀态,直到調用成員函數ResetEvent()将 其重新設為無信号狀态時為止。如果CEvent 類對象為自動事件,則在SetEvent()将事件設定為有信号狀态後,CEvent 類對象由系統自動重置為無信号狀态。

如果該函數執行成功,則傳回非零值,否則傳回零。

  該函數将事件的狀态設定為無信号狀态,并保持該狀态直至SetEvent()被調用時為止。由于自動事件是由系統自動重置,故自動事件不需要調用該函數。如果該函數執行成功,傳回非零值,否則傳回零。我們一般通過調用WaitForSingleObject函數來監視事件狀态。前面我們已經介紹了該函數。由于語言描述的原因,CEvent 類的了解确實有些難度,但您隻要通過仔細玩味下面例程,多看幾遍就可了解。

例程9 MultiThread9

建立一個基于對話框的工程MultiThread9,在對話框IDD_MULTITHREAD9_DIALOG中加入一個按鈕和兩個編輯框控件,按鈕的ID為IDC_WRITEW,标題為“寫‘W’”;兩個編輯框的ID分别為IDC_W和IDC_D,屬性都選中Read-only;

在MultiThread9Dlg.h檔案中聲明兩個線程函數:

在MultiThread9Dlg.cpp檔案中添加如下内容:

為了檔案中能夠正确使用同步類,在檔案開頭添加

定義事件對象和一個字元數組,為了能夠在不同線程間使用,定義為全局變量。

   仔細分析這兩個線程函數, 您就會正确了解CEvent 類。線程WriteD執行到 WaitForSingleObject(eventWriteD.m_hObject,INFINITE);處等待,直到事件eventWriteD為有信号該線程才往下執行,因為eventWriteD對象是自動事件,則當WaitForSingleObject()傳回時,系統自動把eventWriteD對象重置為無信号狀态。

輕按兩下按鈕IDC_WRITEW,添加其響應函數:

編譯并運作程式,單擊“寫‘W’”按鈕,體會事件對象的作用。

<b>C、使用CMutex 類</b>

  互斥對象與臨界區對象很像.互斥對象與臨界區對象的不同在于:互斥對象可以在程序間使用,而臨界區對象隻能在同一程序的各線程間使用。當然,互斥對象也可以用于同一程序的各個線程間,但是在這種情況下,使用臨界區會更節省系統資源,更有效率。

<b>D、使用CSemaphore 類</b>

  當需要一個計數器來限制可以使用某個線程的數目時,可以使用“信号量”對象。CSemaphore 類的對象儲存了對目前通路某一指定資源的線程的計數值,該計數值是目前還可以使用該資源的線程的數目。如果這個計數達到了零,則所有對這個CSemaphore 類對象所控制的資源的通路嘗試都被放入到一個隊列中等待,直到逾時或計數值不為零時為止。一個線程被釋放已通路了被保護的資源時,計數值減1;一個線程完成了對被控共享資源的通路時,計數值增1。這個被CSemaphore 類對象所控制的資源可以同時接受通路的最大線程數在該對象的建構函數中指定。

CSemaphore 類的構造函數原型及參數說明如下:

lInitialCount:信号量對象的初始計數值,即可通路線程數目的初始值;

lMaxCount:信号量對象計數值的最大值,該參數決定了同一時刻可通路由信号量保護的資源的線程最大數目;

後兩個參數在同一程序中使用一般為NULL,不作過多讨論;

  在用CSemaphore 類的構造函數建立信号量對象時要同時指出允許的最大資源計數和目前可用資源計數。一般是将目前可用資源計數設定為最大資源計數,每增加一個線程對共享資源的通路,目前可用資源計數就會減1,隻要目前可用資源計數是大于0的,就可以發出信号量信号。但是目前可用計數減小到0時,則說明目前占用資源的線程數已經達到了所允許的最大數目,不能再允許其它線程的進入,此時的信号量信号将無法發出。線程在處理完共享資源後,應在離開的同時通過ReleaseSemaphore()函數将目前可用資源數加1。

下面給出一個簡單執行個體來說明 CSemaphore 類的用法。

例程10 MultiThread10

建立一個基于對話框的工程MultiThread10,在對話框IDD_MULTITHREAD10_DIALOG中加入一個按鈕和三個編輯框控件,按鈕的ID為IDC_START,标題為“同時寫‘A’、‘B’、‘C’”;三個編輯框的ID分别為IDC_A、IDC_B和IDC_C,屬性都選中Read-only;

在MultiThread10Dlg.h檔案中聲明兩個線程函數:

使用ClassWizard分别給IDC_A、IDC_B和IDC_C添加CEdit類變量m_ctrlA、m_ctrlB和m_ctrlC;

在MultiThread10Dlg.cpp檔案中添加如下内容:

定義信号量對象和一個字元數組,為了能夠在不同線程間使用,定義為全局變量:

添加三個線程函數:

這三個線程函數不再多說。在信号量對象有信号的狀态下,線程執行到WaitForSingleObject語句處繼續執行,同時可用線程數減1;若線程執行到WaitForSingleObject語句時信号量對象無信号,線程就在這裡等待,直到信号量對象有信号線程才往下執行。

輕按兩下按鈕IDC_START,添加其響應函數:

本文轉自feisky部落格園部落格,原文連結:http://www.cnblogs.com/feisky/archive/2009/12/07/1618575.html,如需轉載請自行聯系原作者