天天看點

Windows平台下的多線程程式設計 Win32 API下的多線程程式設計  基于MFC的多線程程式設計  結束語 

線程是程序的一條執行路徑,它包含獨立的堆棧和CPU寄存器狀态,每個線程共享所有的程序資源,包括打開的檔案、信号辨別及動态配置設定的記憶體等。一個程序内的所有線程使用同一個位址空間,而這些線程的執行由系統排程程式控制,排程程式決定哪個線程可執行以及什麼時候執行線程。線程有優先級别,優先權較低的線程必須等到優先權較高的線程執行完後再執行。在多處理器的機器上,排程程式可将多個線程放到不同的處理器上去運作,這樣可使處理器任務平衡,并提高系統的運作效率。 

Windows是一種多任務的作業系統,在Windows的一個程序内包含一個或多個線程。32位Windows環境下的Win32 API提供了多線程應用程式開發所需要的接口函數,而利用VC中提供的标準C庫也可以開發多線程應用程式,相應的MFC類庫封裝了多線程程式設計的類,使用者在開發時可根據應用程式的需要和特點選擇相應的工具。為了使大家能全面地了解Windows多線程程式設計技術,本文将重點介紹Win32 API和MFC兩種方式下如何編制多線程程式。 

多線程程式設計在Win32方式下和MFC類庫支援下的原理是一緻的,程序的主線程在任何需要的時候都可以建立新的線程。當線程執行完後,自動終止線程; 當程序結束後,所有的線程都終止。所有活動的線程共享程序的資源,是以,在程式設計時需要考慮在多個線程通路同一資源時産生沖突的問題。當一個線程正在通路某程序對象,而另一個線程要改變該對象,就可能會産生錯誤的結果,程式設計時要解決這個沖突。 

Win32 API是Windows作業系統核心與應用程式之間的界面,它将核心提供的功能進行函數包裝,應用程式通過調用相關函數而獲得相應的系統功能。為了向應用程式提供多線程功能,Win32 API函數集中提供了一些處理多線程程式的函數集。直接用Win32 API進行程式設計具有很多優點: 基于Win32的應用程式執行代碼小,運作效率高,但是它要求程式員編寫的代碼較多,且需要管理所有系統提供給程式的資源。用Win32 API直接編寫程式要求程式員對Windows系統核心有一定的了解,會占用程式員很多時間對系統資源進行管理,因而程式員的工作效率降低。 

Win32函數庫中提供了操作多線程的函數,包括建立線程、終止線程、建立互斥區等。在應用程式的主線程或者其他活動線程中建立新的線程的函數如下: 

如果建立成功則傳回線程的句柄,否則傳回NULL。建立了新的線程後,該線程就開始啟動執行了。但如果在dwCreationFlags中使用了CREATE_SUSPENDED特性,那麼線程并不馬上執行,而是先挂起,等到調用ResumeThread後才開始啟動線程,在這個過程中可以調用下面這個函數來設定線程的優先權: 

BOOL SetThreadPriority(HANDLE hThread,int nPriority); 

當調用線程的函數傳回後,線程自動終止。如果需要線上程的執行過程中終止則可調用函數: 

VOID ExitThread(DWORD dwExitCode); 

如果線上程的外面終止線程,則可調用下面的函數: 

BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode); 

但應注意: 該函數可能會引起系統不穩定,而且線程所占用的資源也不釋放。是以,一般情況下,建議不要使用該函數。 

如果要終止的線程是程序内的最後一個線程,則線程被終止後相應的程序也應終止。 

線上程體内,如果該線程完全獨立,與其他線程沒有資料存取等資源操作上的沖突,則可按照通常單線程的方法進行程式設計。但是,在多線程處理時情況常常不是這樣,線程之間經常要同時通路一些資源。由于對共享資源進行通路引起沖突是不可避免的,為了解決這種線程同步問題,Win32 API提供了多種同步控制對象來幫助程式員解決共享資源通路沖突。在介紹這些同步對象之前先介紹一下等待函數,因為所有控制對象的通路控制都要用到這個函數。 

Win32 API提供了一組能使線程阻塞其自身執行的等待函數。這些函數在其參數中的一個或多個同步對象産生了信号,或者超過規定的等待時間才會傳回。在等待函數未傳回時,線程處于等待狀态,此時線程隻消耗很少的CPU時間。使用等待函數既可以保證線程的同步,又可以提高程式的運作效率。最常用的等待函數是: 

DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds); 

而函數WaitForMultipleObject可以用來同時監測多個同步對象,該函數的聲明為: 

DWORD WaitForMultipleObject(DWORD nCount,CONST HANDLE *lpHandles,BOOL bWaitAll,DWORD dwMilliseconds); 

(1)互斥體對象 

Mutex對象的狀态在它不被任何線程擁有時才有信号,而當它被擁有時則無信号。Mutex對象很适合用來協調多個線程對共享資源的互斥通路。可按下列步驟使用該對象: 

首先,建立互斥體對象,得到句柄: 

HANDLE CreateMutex(); 

然後,線上程可能産生沖突的區域前(即通路共享資源之前)調用WaitForSingleObject,将句柄傳給函數,請求占用互斥對象: 

dwWaitResult = WaitForSingleObject(hMutex,5000L); 

共享資源通路結束,釋放對互斥體對象的占用: 

ReleaseMutex(hMutex); 

互斥體對象在同一時刻隻能被一個線程占用,當互斥體對象被一個線程占用時,若有另一線程想占用它,則必須等到前一線程釋放後才能成功。 

(2)信号對象 

信号對象允許同時對多個線程共享資源進行通路,在建立對象時指定最大可同時通路的線程數。當一個線程申請通路成功後,信号對象中的計數器減一,調用ReleaseSemaphore函數後,信号對象中的計數器加一。其中,計數器值大于或等于0,但小于或等于建立時指定的最大值。如果一個應用在建立一個信号對象時,将其計數器的初始值設為0,就阻塞了其他線程,保護了資源。等初始化完成後,調用ReleaseSemaphore函數将其計數器增加至最大值,則可進行正常的存取通路。可按下列步驟使用該對象: 

首先,建立信号對象: 

HANDLE CreateSemaphore(); 

或者打開一個信号對象: 

HANDLE OpenSemaphore(); 

然後,線上程通路共享資源之前調用WaitForSingleObject。 

共享資源通路完成後,應釋放對信号對象的占用: 

ReleaseSemaphore(); 

(3)事件對象 

事件對象(Event)是最簡單的同步對象,它包括有信号和無信号兩種狀态。線上程通路某一資源之前,需要等待某一事件的發生,這時用事件對象最合适。例如:隻有在通信端口緩沖區收到資料後,監視線程才被激活。 

事件對象是用CreateEvent函數建立的。該函數可以指定事件對象的類和事件的初始狀态。如果是手工重置事件,那麼它總是保持有信号狀鋇接肦esetEvent函數重置成無信号的事件。如果是自動重置事件,那麼它的狀态在單個等待線程釋放後會自動變為無信号的。用SetEvent可以把事件對象設定成有信号狀态。在建立事件時,可以為對象命名,這樣其他程序中的線程可以用OpenEvent函數打開指定名字的事件對象句柄。 

(4)排斥區對象 

在排斥區中異步執行時,它隻能在同一程序的線程之間共享資源處理。雖然此時上面介紹的幾種方法均可使用,但是,使用排斥區的方法則使同步管理的效率更高。 

使用時先定義一個CRITICAL_SECTION結構的排斥區對象,在程序使用之前調用如下函數對對象進行初始化: 

VOID InitializeCriticalSection(LPCRITICAL_SECTION); 

當一個線程使用排斥區時,調用函數:EnterCriticalSection或者TryEnterCriticalSection; 

當要求占用、退出排斥區時,調用函數LeaveCriticalSection,釋放對排斥區對象的占用,供其他線程使用。 

MFC是微軟的VC開發內建環境中提供給程式員的基礎函數庫,它用類庫的方式将Win32 API進行封裝,以類的方式提供給開發者。由于其快速、簡捷、功能強大等特點深受廣大開發者喜愛。是以,建議使用MFC類庫進行應用程式的開發。 

在VC++附帶的MFC類庫中,提供了對多線程程式設計的支援,基本原理與基于Win32 API的設計一緻,但由于MFC對同步對象做了封裝,是以實作起來更加友善,避免了對象句柄管理上的煩瑣工作。 

在MFC中,線程分為兩種:工作線程和使用者接口線程。工作線程與前面所述的線程一緻,使用者接口線程是一種能夠接收使用者的輸入、處理事件和消息的線程。 

工作線程程式設計較為簡單,設計思路與前面所講的基本一緻: 一個基本函數代表了一個線程,建立并啟動線程後,線程進入運作狀态; 如果線程用到共享資源,則需要進行資源同步處理。這種方式建立線程并啟動線程時可調用函數: 

參數pfnThreadProc是線程執行體函數,函數原形為: UINT ThreadFunction( LPVOID pParam)。 

參數pParam是傳遞給執行函數的參數; 

參數nPriority是線程執行權限,可選值: 

THREAD_PRIORITY_NORMAL、THREAD_PRIORITY_LOWEST、THREAD_PRIORITY_HIGHEST、THREAD_PRIORITY_IDLE。 

參數dwCreateFlags是線程建立時的标志,可取值CREATE_SUSPENDED,表示線程建立後處于挂起狀态,調用ResumeThread函數後線程繼續運作,或者取值“0”表示線程建立後處于運作狀态。 

傳回值是CWinThread類對象指針,它的成員變量m_hThread為線程句柄,在Win32 API方式下對線程操作的函數參數都要求提供線程的句柄,是以當線程建立後可以使用所有Win32 API函數對pWinThread->m_Thread線程進行相關操作。 

注意:如果在一個類對象中建立和啟動線程時,應将線程函數定義成類外的全局函數。 

基于MFC的應用程式有一個應用對象,它是CWinApp派生類的對象,該對象代表了應用程序的主線程。當線程執行完并退出線程時,由于程序中沒有其他線程存在,程序自動結束。類CWinApp從CWinThread派生出來,CWinThread是使用者接口線程的基本類。我們在編寫使用者接口線程時,需要從CWinThread派生我們自己的線程類,ClassWizard可以幫助我們完成這個工作。 

先用ClassWizard派生一個新的類,設定基類為CwinThread。注意:類的DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏是必需的,因為建立線程時需要動态建立類的對象。根據需要可将初始化和結束代碼分别放在類的InitInstance和ExitInstance函數中。如果需要建立視窗,則可在InitInstance函數中完成。然後建立線程并啟動線程。可以用兩種方法來建立使用者接口線程,MFC提供了兩個版本的AfxBeginThread函數,其中一個用于建立使用者接口線程。第二種方法分為兩步進行:首先,調用線程類的構造函數建立一個線程對象;其次,調用CWinThread::CreateThread函數來建立該線程。線程建立并啟動後,線上程函數執行過程中一直有效。如果是線程對象,則在對象删除之前,先結束線程。CWinThread已經為我們完成了線程結束的工作。 

前面我們介紹了Win32 API提供的幾種有關線程同步的對象,在MFC類庫中對這幾個對象進行了類封裝,它們有一個共同的基類CSyncObject,它們的對應關系為: Semaphore對應CSemaphore、Mutex對應CMutex、Event對應CEvent、CriticalSection對應CCriticalSection。另外,MFC對兩個等待函數也進行了封裝,即CSingleLock和CMultiLock。因四個對象用法相似,在這裡就以CMutex為例進行說明: 

建立一個CMutex對象: 

CMutex mutex(FALSE,NULL,NULL); 

或CMutex mutex; 

當各線程要通路共享資源時使用下面代碼: 

CSingleLock sl(&mutex); 

sl.Lock(); 

if(sl.IsLocked()) 

//對共享資源進行操作... 

sl.Unlock(); 

如果使用者的應用程式需要多個任務同時進行相應的處理,則使用多線程是較理想的選擇。這裡,提醒大家注意的是在多線程程式設計時要特别小心處理資源共享問題以及多線程調試問題

繼續閱讀