摘要
本文從分析源代碼的角度介紹Delphi5中的TThread類的封裝和運行機理,介紹了TThread類的優(yōu)缺點。
關鍵詞:Delphi5,TThread,Windows API
目錄
1.概述
2.剖析TThread類
2.1 TThread的優(yōu)點
2.2 TThread的封裝和運行機理
3.結束語
4.致謝
5.參考文獻
全文
1.概述
根據(jù)Windows SDK文檔的說明,在Windows線程中的運行實體是類型為:function ThreadFunc(Parameter: pointer): integer的函數(shù)(翻譯成Delphi的格式)。但是我們都知道,在Delphi中線程被封裝成一個TThread類。為什么Delphi要將它封裝成一個類?Delphi是如何封裝的呢?我們怎樣才能充分的利用兩者的優(yōu)點?這就是本下面要介紹的。
2.剖析TThread類
2.1 TThread的優(yōu)點
將線程作為類來封裝有著許多優(yōu)點。首先它能清晰、安全的界限線程相關的局部變量和進程相關的全局變量。類——對象的模型到實體的映射關系保證了聲明在類中的任何變量都是局部的,聲明在類外的任何變量都是全局的。所以在寫新線程的Execute函數(shù)只要注意對類外部的變量、方法的訪問就可以了,至于類內部的變量、方法則可以任意使用而不用考慮同步的問題。將線程封裝成類的更重要的好處是寫新線程的時候可以充分利用類的優(yōu)點。你可以通過繼承來重用父類的功能,這實在是一個激動人心的功能。
2.2 TThread的封裝和運行機理
既然已經(jīng)知道將線程封裝成類有諸多好處,作為一個稱職的程序員一定會去了解Delphi是如何將線程封裝成類的,有沒有更好的封裝的方法的。
Delphi5中TThread類是這樣聲明的:
{ TThread }
EThread = class(Exception);
TThreadMethod = PRocedure of object; 
TThreadPriority = (tpIdle, tpLowest, tpLower, tpNormal, tpHigher, tpHighest, 
tpTimeCritical); 
TThread = class 
private 
FHandle: THandle; 
FThreadID: THandle; 
FTerminated: Boolean; 
FSuspended: Boolean; 
FFreeOnTerminate: Boolean; 
FFinished: Boolean; 
FReturnValue: Integer; 
FOnTerminate: TNotifyEvent; 
FMethod: TThreadMethod; 
FSynchronizeException: TObject; 
procedure CallOnTerminate; 
function GetPriority: TThreadPriority; 
procedure SetPriority(Value: TThreadPriority); 
procedure SetSuspended(Value: Boolean); 
protected 
procedure DoTerminate; virtual; 
procedure Execute; virtual; abstract; 
procedure Synchronize(Method: TThreadMethod); 
property ReturnValue: Integer read FReturnValue write FReturnValue; 
property Terminated: Boolean read FTerminated; 
public 
constructor Create(CreateSuspended: Boolean); 
destructor Destroy; override; 
procedure Resume; 
procedure Suspend; 
procedure Terminate; 
function WaitFor: LongWord; 
property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate; 
property Handle: THandle read FHandle; 
property Priority: TThreadPriority read GetPriority write SetPriority; 
property Suspended: Boolean read FSuspended write SetSuspended; 
property ThreadID: THandle read FThreadID; 
property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate; 
end; 
準確地說,TThread對象是一個帶有線程實例的不可見窗體對象(長寬都為0),我把這個窗體叫做線程窗體。這個線程窗體有該TThread類的所有對象共享。TThread在構造的時候線程是否第一次創(chuàng)建,如果是就創(chuàng)建線程窗體,然后增加線程計數(shù),最后才建立線程實例。同理,TThread對象在銷毀的時候,先減少線程計數(shù),然后判斷計數(shù)是否為0,如果是就銷毀線程窗體。 
為什么要建立一個線程窗體呢?答案就是TThread中的同步函數(shù)Synchronize()的需要。線程對象存取其他VCL的屬性時與其他線程的同步機制是通過消息隊列來實現(xiàn)的。當線程函數(shù)執(zhí)行Synchronize()時,他就向線程窗體發(fā)送一條CM_EXECPROC消息。因為線程窗體是進程的一個窗體(雖然它不可見),所以發(fā)向線程窗體的消息都會進入進程消息隊列,而消息隊列的串行處理的特性保證不會出現(xiàn)訪問沖突。這是一個簡單而有效的解決方案。我不知道有沒有人在控制臺程序中應用多線程,如果有的話,TThread類可能就不太適合了。這種情況下要么直接應用線程函數(shù),要么自己寫一個新的TNewThread類了。
Delphi是在TThread類的外面聲明了一個局部函數(shù)ThreadProc。這個函數(shù)就是Windows SDK中介紹的線程函數(shù),其聲明如下:
function ThreadProc(Thread: TThread): Integer; 
var 
FreeThread: Boolean; 
begin 
try 
Thread.Execute; 
finally 
FreeThread := Thread.FFreeOnTerminate; 
Result := Thread.FReturnValue; 
Thread.FFinished := True; 
Thread.DoTerminate; 
if FreeThread then Thread.Free; 
EndThread(Result); 
end; 
end; 
Delphi沒有將線程函數(shù)作為TThread的一個成員函數(shù),我想把ThreadProc放到TThread的Proctected段中TThread的靈活性可能會更好一點,不過現(xiàn)在的方法也不錯。可以看到ThreadProc以TThread對象作為Parameter參數(shù)。這樣可以保證TThread對象進入線程的堆棧中,一個TThread對象不破壞另一個同類型TThread對象的數(shù)據(jù)。當然,創(chuàng)建線程的線程還是可以訪問新線程中的數(shù)據(jù)的,Terminate過程就是這樣做的。所以TThread的數(shù)據(jù)還是可能被其他線程破壞的。所以外部線程要訪問線程的數(shù)據(jù)要小心處理,Terminate()是一個比較典型的:外部線程只寫,內部線程只讀就能很好的工作,如果兩個線程都又讀又寫就可能導致邏輯混亂。 
TThread類在構造線程實例是沒有直接調用CreateThread() API函數(shù),而是使用了一個BeginThread()函數(shù)。不知是什么原因,該函數(shù)并沒有相應的Delphi Help文檔,只是在“TThreadFunc type”的介紹中一筆帶過。可能是Borland認為它的參數(shù)在以后還會修改吧。不過該函數(shù)和CreateThread() API的參數(shù)是一模一樣的。這是一個讓人興奮的地方,因為BeginThread()加入了Windows API沒有的異常處理功能。有意思的是,Delphi在BeginThread()由創(chuàng)建了一個新的線程函數(shù),而把原來的線程函數(shù)和參數(shù)打包成TThreadRec作為新函數(shù)的Parameter。有關Delphi5中BeginThread的定義如下:
type 
PThreadRec = ^TThreadRec; 
TThreadRec = record 
Func: TThreadFunc; 
Parameter: Pointer; 
end; 
function ThreadWrapper(Parameter: Pointer): Integer; stdcall; 
asm 
CALL _FpuInit 
XOR ECX,ECX 
PUSH EBP 
PUSH offset _ExceptionHandler //新增加的Delphi的異常機制 
MOV EDX,FS:[ECX] 
PUSH EDX 
MOV EAX,Parameter 
MOV FS:[ECX],ESP 
MOV ECX,[EAX].TThreadRec.Parameter 
MOV EDX,[EAX].TThreadRec.Func 
PUSH ECX 
PUSH EDX 
CALL _FreeMem 
POP EDX 
POP EAX 
CALL EDX //調用原來的線程函數(shù) 
XOR EDX,EDX 
POP ECX 
MOV FS:[EDX],ECX 
POP ECX 
POP EBP 
end; 
function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord; 
ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord; 
var ThreadId: LongWord): Integer; 
var 
P: PThreadRec; 
begin 
New(P); 
P.Func := ThreadFunc; 
P.Parameter := Parameter; 
IsMultiThread := TRUE; 
Result := CreateThread(SecurityAttributes, StackSize, @ThreadWrapper, P, 
CreationFlags, ThreadID); 
end; 
讓人覺得美中不足的地方是TThread類在調用BeginThread時傳遞的SercurityAttributes和StackSize參數(shù)分別是nil和0,使BeginThread()在調用CreateThread()時使用了缺省的安全設置和默認堆棧大小。有關這兩個參數(shù)代表什么意義請查閱Windows SDK文檔。 
3.結束語
由于時間倉促,簡單介紹我認為Delphi的幫助文檔中沒有說明的部分。不知你看后有什么疑惑或是覺得我什么講的不對的地方,請來信告知: zg@hzhistar.com 。請多多指教!
4.致謝
"其實,你這篇文章只適用于Delphi5,Delphi6已經(jīng)改變了Synchronize的做法,改用事件(Event)和臨界區(qū)(CriticalSection)的配合來進行同步多線程對VCL控件的訪問。其它還有些少改動的地方,相信你看源碼就會發(fā)現(xiàn)。 
另外,(或許你已經(jīng)知道了)Delphi的文檔也很清楚地說明了,調用BeginThread和EndThread來替代Win32API的CreateThread和ExitThread(其實《Windows 核心編程》也指出了應使用開發(fā)環(huán)境提供的_beginthreadex等函數(shù),具體原因看書吧),至于Delphi,調用BeginThread的一個非常重要的作用就是將全局變量IsMultiThread設為True,因為Delphi的許多運行機制是當該變量為True時才是線程安全的,例如GetMem和FreeMem函數(shù)。" 
——摘自 "hgd" <hgd01@263.net> 
上述的朋友給了我嚴謹?shù)呐u和暖和的鼓舞,我在這里表示衷心的感謝!如果你給我提供了你的想法,我就在這里寫上你的大名。:)
5.參考文獻
1.《Windows核心編程》,機械工業(yè)出版社,2000年5月(雖然中文翻譯奇爛) 
2.《Microsoft Platform SDK》,microsoft, 2001年8月 
3.《Delphi 5.0 幫助文檔》、Delphi 5.0源代碼 
新聞熱點
疑難解答