delphi多線程編程
━━━━━━━━━━━━━━━━━━━━━━━━━━
本章描述Windows 95與Windows NT的基于線程的多任務(wù)設(shè)計。
15.1 線 程
我們知道,Windows 95支持兩種形式的多任務(wù)。第一種類型是基于進(jìn)程的機(jī)制,這也是Windows從一開始就支持的多處理類型。進(jìn)程本質(zhì)上是指正在執(zhí)行著的程序,在基于進(jìn)程的多任務(wù)環(huán)境下,兩個至多個進(jìn)程可以并發(fā)地執(zhí)行。第二種類型是基于線程(thread)的機(jī)制,基于線程的多任務(wù)對于多數(shù)Windows用戶和程序員而言是一個嶄新的概念,因為Windows 95以前的Windows版本不能支持線程的概念(Windows NT除外)。
線程是指進(jìn)程中的一個執(zhí)行流。多個線程可以并發(fā)地運行于同一個進(jìn)程中。
在Windows 95/98/NT中,每一個進(jìn)程擁有至少一個線程,允許同時執(zhí)行兩個或多個線程。并由我們的程序控制它們?;诰€程的多任務(wù)允許同一程序的不同部分(線程)并發(fā)地執(zhí)行。這樣,程序員就能夠?qū)懗龇浅8咝У某绦?,因為程序員能夠定義執(zhí)行線程并管理程序的執(zhí)行方式,能夠完全地控制程序片段的執(zhí)行。例如,可以在一個程序中指定一個線程執(zhí)行文件排序工作,指定另外一個線程負(fù)責(zé)收集來自某個遠(yuǎn)程資源的信息,指定又一線程完成用戶輸入的工作。因為處于多線程多任務(wù)環(huán)境,每一線程都能夠并發(fā)地執(zhí)行,這樣就能充分地利用CPU時間。
通常,多線程處理使程序運行速度減慢,除非我們有多線程CPU,以及可以在處理器中分離線程的操作系統(tǒng)。
基于線程的多任務(wù)使得同步功能特征顯得更為重要。既然多個線程(及進(jìn)程)可以并發(fā)執(zhí)行,那么必須適當(dāng)?shù)貐f(xié)調(diào)線程間的執(zhí)行順序以使其能同步訪問共享資源與內(nèi)存,從而使程序編寫起來顯得更加復(fù)雜。Windows 95增加了一個完整的子系統(tǒng)以支持同步機(jī)制,其中的一些關(guān)鍵特征將在本章后進(jìn)一步地討論。
15.2 線 程 類
所有進(jìn)程至少都擁有一個執(zhí)行線程,為了討論方便,我們稱該執(zhí)行線程為主線程。然而,在一個程序中,除了主線程之外,可能還創(chuàng)建有一個或更多的執(zhí)行線程。通常,一個線程一旦創(chuàng)建即開始執(zhí)行。因此,一個進(jìn)程最開始作為一個執(zhí)行線程而被啟動,以后它還可以創(chuàng)建更多的執(zhí)行線程。通過這種機(jī)制,基于線程的多任務(wù)得以實現(xiàn)。
編寫基于線程的程序,既可以使用Windows 95提供的Win32應(yīng)用程序接口函數(shù)(API),也可以使用Delphi 5.0提供的線程對象。而使用Delphi 5.0的線程對象(TThread對象),則可使編寫線程程序變得簡單、高效。TThread對象提供了許多特性和方法(成員函數(shù)),你只要根據(jù)工作需要對這些函數(shù)或方法進(jìn)行重寫,即可在程序中實現(xiàn)多線程機(jī)制。
Tthread類是TObject對象的直接派生類。與其它大多數(shù)Delphi類和構(gòu)件不同的是,你不能在程序中直接使用該對象,而必須從Tthread類產(chǎn)生一個新的派生類,并對需要使用的方法進(jìn)行重寫,以重載(Override)Tthread類的方法。下面對Tthread類的部分特性和方法進(jìn)行討論(除非特別說明,所有討論的特性和方法都是Public):
15.2.1 線程類特性
線程類包括以下幾種特性:
1.FreeOnTerminate特性:該特性為布爾類型,只能在運行時間使用。 該特性決定線程結(jié)束時,是由VCL自動消除(destroy)線程對象(值為True),還是你自己負(fù)責(zé)消除線程對象(值為False)。FreeOnTerminate缺省值為 False。
2.Thandle特性:每一個執(zhí)行線程都有一個句柄,Win32 API大多數(shù)線程函數(shù)都需要用到句柄。該特性就用于保存線程的句柄,通過讀取Thandle特性的值,可以獲得線程的句柄,從而可調(diào)用Win32 API函數(shù)來執(zhí)行控制線程的操作。
3.Priority特性:該特性動態(tài)設(shè)置線程調(diào)度的優(yōu)先級,只能在運行時間使用。優(yōu)先級的缺省值為tpNormal,但你可以根據(jù)需要進(jìn)行設(shè)置,以改變線程調(diào)度的優(yōu)先級。詳細(xì)介紹請看12.3節(jié)。
4.ReturnValue特性:該特性為整數(shù)類型,且只能由線程對象的派生類調(diào)用或訪問(Protected特性)。該特性等價于函數(shù)的Result變量,你可以使用該特性來指示線程執(zhí)行的成功或失敗。
5.Suspended特性:該特性為布爾類型,用于決定線程是否掛起。被掛起的線程停止執(zhí)行,直到該線程被恢復(fù)。設(shè)置線程的Suspended特性為TRUE,可以掛起一個線程;設(shè)置線程的Suspended特性為FALSE,可以恢復(fù)一個線程的執(zhí)行。
6.Terminated特性:該特性為布爾類型,且為Protected特性,只讀,只能由線程對象的派生類調(diào)用或訪問。該特性決定線程是否應(yīng)該中止執(zhí)行。如果該特性值為TRUE,表示線程執(zhí)行即將結(jié)束,這時程序應(yīng)立即退出線程的Execute方法。你也可以通過調(diào)用Terminate方法來強行設(shè)置線程的Terminated特性為TRUE,從而達(dá)到退出線程執(zhí)行的目的。
7.ThreadID特性: 該特性用于保存線程的標(biāo)識,有些Win32 API線程函數(shù)需要使用該標(biāo)識。通過讀取ThreadID特性的值,可以獲得線程的標(biāo)識,從而可調(diào)用Win32 API函數(shù)來執(zhí)行其它控制線程的操作。
15.2.2 線程類方法
線程類主要包括以下幾種方法:
1.Create構(gòu)造函數(shù):參數(shù)CreateSuspended用來選擇是馬上啟動線程(值為False)還是暫停它(值為True)。
2.DoTerminate過程:該過程只能由線程對象內(nèi)部方法調(diào)用,用于與主VCL線程的同步,并產(chǎn)生OnTerminate事件。一般來說,當(dāng)線程終止時,線程會自動調(diào)用DoTermiante過程,程序員一般不需要重寫該過程的代碼。
3.Execute過程:該方法開始一個線程的執(zhí)行,你必須在派生的線程類中重寫該過程,以實現(xiàn)線程的功能。當(dāng)使用Windows API時,這段代碼應(yīng)該放置在線程函數(shù)中。Execute方法返回時,終止線程的執(zhí)行,釋放線程堆棧,并調(diào)用OnTerminate事件驅(qū)動程序(如果有的話)。Execute方法必須周期性地檢測Terminated特性,如果為TRUE,Execute方法必須立即返回。如果線程返回失敗,說明調(diào)用Termianted方法時,線程沒有終止。
4.Resume過程:該方法恢復(fù)一個被掛起的線程的執(zhí)行。Resume方法調(diào)用可以嵌套,如果你執(zhí)行了多次Suspend,則線程實際恢復(fù)運行前,你必須執(zhí)行同樣次數(shù)的Resume調(diào)用。
5.Suspend過程:該方法暫停一個線程的執(zhí)行。暫停執(zhí)行的線程可以調(diào)用Resume方法來恢復(fù)執(zhí)行。Suspend方法調(diào)用可以嵌套,如果你執(zhí)行了多次Suspend,則線程實際恢復(fù)運行前,你必須執(zhí)行同樣次數(shù)的Resume調(diào)用。
6.Synchronize過程:在Delphi 5.0的多線程編程中,各種VCL構(gòu)件都是臨界資源,只能由主線程使用。其它線程要使用這些VCL構(gòu)件,必須使用Synchronize方法,通過傳遞使用了VCL構(gòu)件的方法,就可避免多線程與VCL構(gòu)件的沖突,避免重入問題。該過程帶有唯一一個TThreadMethod類型的參數(shù)是一個不接收參數(shù)的對象方法,用于指定在線程對象中的方法。
7.Terminate過程:該方法設(shè)置Terminated特性為TRUE,并通知你的線程,線程執(zhí)行將終止。Execute方法必須周期性地檢測Terminated特性,如果為TRUE,必須立即返回。
8.WaitFor函數(shù):該方法等待線程執(zhí)行的終止,然后返回ReturnValue特性的值(整型)。如果線程不終止,WaitFor函數(shù)就不會返回,因此,在調(diào)用WaitFor函數(shù)后,必須確保線程退出,這可以通過終止Execute方法的執(zhí)行或當(dāng)Terminated特性值為TRUE時退出來實現(xiàn)。另外,如果線程使用了Synchronize方法,則不要在主線程中使用WaitFor方法,因為這樣一來易引起死鎖,或?qū)е赂鞣NEthread例外的發(fā)生。Synchronize在主線程允許同步的方法執(zhí)行之前一直等待,直到主線程進(jìn)入消息循環(huán),如果主線程調(diào)用了WaitFor,它就不能進(jìn)入消息循環(huán),Synchronize也不會返回,TThread會檢查到這種錯誤,從而引起Ethread例外。如果WaitFor已被調(diào)用,而Synchronize又一直等待,則TThread不會檢查到這種錯誤,你的程序?qū)⑦M(jìn)入死鎖。
9.OnTerminate過程:該過程是OnTerminate事件的驅(qū)動程序。OnTerminate事件發(fā)生在線程的Execute方法已經(jīng)返回,TThread結(jié)束線程之前。該事件驅(qū)動程序只能在主線程使用,可以調(diào)用各種VCL方法和特性。
15.3 創(chuàng)建多線程程序
下面以一個例子說明創(chuàng)建多線程執(zhí)行程序的過程。在圖15.1中,程序?qū)?chuàng)建兩個執(zhí)行5000次的循環(huán)的線程。每個線程在執(zhí)行循環(huán)的每一次疊代后,將顯示循環(huán)的次數(shù)。當(dāng)運行該程序時,會發(fā)現(xiàn)兩個線程是并發(fā)執(zhí)行的。
15.3.1 創(chuàng)建多線程
在Delphi中,你即可以通過直接書寫線程代碼來創(chuàng)建線程,也可以Delphi的File|New命令向當(dāng)前項目文件加入一個線程對象來實現(xiàn)。兩者結(jié)果都一樣,都是產(chǎn)生一個Tthread類的派生類。下面就使用后一種方法進(jìn)行說明。
選擇File菜單的New命令,打開New Items對話框,選擇New頁標(biāo)簽下的Thread Object圖標(biāo)(見圖15.2),打開New Thread Object對話框,在New Thread Object對話框的Class Name編輯框,輸入創(chuàng)建線程的類名,然后選擇OK按鈕,則Delphi生成一個新的代碼文件,該代碼文件即為新線程類的代碼文件。
程序如下:
unit Unit2;
interface
uses Classes;
type
MyThread = class(TThread)
private
{ Private declarations }
protected procedure Execute;
override;
end;
implementation
{ Important: Methods and properties of objects in VCL can only be used
in a method called using Synchronize, for example,
Synchronize(UpdateCaption); and UpdateCaption could look like,
procedure MyThread.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end; }
{ MyThread }
procedure
MyThread.Execute;
begin
{ Place thread code here }
end;
end.
該代碼文件生成一個線程類的派生類,并提供了需要派生類覆蓋的方法Execute說明。文件中還包含如何編寫處理VCL構(gòu)件方法的說明信息?,F(xiàn)根據(jù)本程序的需要,修改代碼文件,并將其保存為ThreadUnit.Pas。
interface
uses Classes, StdCtrls,SysUtils;
type
MyThread = class(TThread)
private
{ Private declarations }
AEdit:TEdit; MaxLoop:Integer; CurrentLoop: Integer;
protected
procedure Execute; override;
procedure DisLoop;
public
constructor Create(Edit:TEdit;Max:Integer);
end;
implementation
{ MyThread }
constructor MyThread.Create(Edit:TEdit;Max:Integer);
begin
inherited Create(False);
AEdit:=Edit;
MaxLoop:=Max;
FreeOnTerminate := True;
end;
procedure MyThread.DisLoop;
begin
AEdit.text:=InttoStr(CurrentLoop);
end;
procedure MyThread.Execute;
var I:Integer;
begin
for I:=0 to MaxLoop Do
begin
CurrentLoop:=I;
Synchronize(DisLoop);
if Terminated then Exit;
end;
end;
end.
下面就有關(guān)問題進(jìn)行說明:
1.線程類中增加的AEdit,MaxLoop,CurrentLoop三個用于控制循環(huán)次數(shù)的顯示,由于每個線程的這三個特性都不相同,且都需要訪問VCL構(gòu)件,因此它們都設(shè)置為私有特性,不能被繼承,也不能在線程類外使用,當(dāng)類實例化時后,這些特性的值由Create拷入并初始化。
2.增加的DisLoop方法用于向編輯框構(gòu)件寫入循環(huán)次數(shù)。Execute方法通過使用Synchronize調(diào)用DisLoop,從而解決了使用VCL構(gòu)件時多線沖突問題。
3.構(gòu)造函數(shù)Create覆蓋了原線程類的構(gòu)造函數(shù),并將傳入的參數(shù)Edit(對應(yīng)的編輯框件)和Max(最大循環(huán)次數(shù))賦給實例化的對象。
4.線程一旦創(chuàng)建,就執(zhí)行Execute方法,每循環(huán)一次,就將循環(huán)次數(shù)顯示在編輯框.
15.3.2 啟動線程
本程序中,用戶通過單擊Start按鈕來開始兩個線程的執(zhí)行,下面是單擊Start按鈕的事件驅(qū)動程序:
procedure TParaComupte.StartButtonClick(Sender: TObject);
begin
ThreadsRunning:=2;
MyThread1:=MyThread.Create(Edit1,5000);
MyThread1.OnTerminate := ThreadDone;
MyThread2:=MyThread.Create(Edit2,5000);
MyThread2.OnTerminate := ThreadDone;
StartButton.Enabled := False;
end;
該事件驅(qū)動程序創(chuàng)建兩個線程,并傳入有關(guān)參數(shù),ThreadsRunning全局變量用于說明創(chuàng)建線程的個數(shù),ThreadDonee用于定義當(dāng)線程執(zhí)行結(jié)束時(發(fā)生OnTerminate事件)執(zhí)行的過程,其代碼為:
procedure TParaCompute.ThreadDone(Sender: TObject);
begin
dec(ThreadsRunning);
if ThreadsRunning=0 then
StartButton.Enabled := True;
end;
每結(jié)束一個線程,ThreadsRunning就減1,當(dāng)ThreadsRunning值為0時,表示線程全部執(zhí)行完畢,Start按鈕可以接收用戶輸入。
15.3.3 線程的暫停、恢復(fù)與終止
為了說明問題,本節(jié)在窗體中增加下列按鈕構(gòu)件Suspend、Resume、Terminate(見圖15.4)。Suspend暫停線程1的執(zhí)行,Resume恢復(fù)暫停了的線程,Terminate則終止線程的執(zhí)行。
在線程類一節(jié),我們介紹了Suspended特性。該特性為布爾類型,用于決定線程是否掛起。設(shè)置線程的Suspended特性為TRUE,可以掛起一個線程;設(shè)置線程的Suspended特性為FALSE,可以恢復(fù)一個線程的執(zhí)行。而執(zhí)行Terminate過程,則可設(shè)置Terminated特性為TRUE,并通知你的線程,線程執(zhí)行將終止。
如MyThread1.Suspended:=True;
15.4 線程的優(yōu)先級
每一個線程與一個優(yōu)先級相聯(lián)。線程優(yōu)先級用于確定一個線程收到多少CPU時間。線程類的Priority特性用于獲取和設(shè)置線程調(diào)度的優(yōu)先級。優(yōu)先級的缺省值為tpNormal,但你可以根據(jù)需要進(jìn)行設(shè)置,以改變線程調(diào)度的優(yōu)先級。Delphi的TThreadPriority枚舉類型定義了Priority特性所有可能的值。這些值包括:
tpIdle:這是線程的最低優(yōu)先級。只有當(dāng)系統(tǒng)閑置(idle),無其它程序運行時,才執(zhí)行tpIdle優(yōu)先級的線程,Windows也不會為了執(zhí)行tpIdle優(yōu)先級的線程而中斷其它線程的執(zhí)行。
tpLowest:該優(yōu)先級比tpNormal優(yōu)先級低兩級。
tpLower: 該優(yōu)先級比tpNormal優(yōu)先級低一級。
tpNormal:該優(yōu)先級是線程的正常優(yōu)先級。
tpHigher:該優(yōu)先級比tpNormal優(yōu)先級高一級。
tpHighest:該優(yōu)先級比tpNormal優(yōu)先級高兩級。
tpTimeCritical:該優(yōu)先級是線程的最高優(yōu)先級