TThread

根據Windows SDK文檔的說明,在Windows執行緒中的運行實體是類型為:function ThreadFunc(Parameter: pointer): integer的函式(翻譯成Delphi的格式)。

概述

根據WindowsSDK文檔的說明,在Windows執行緒中的運行實體是類型為:function ThreadFunc(Parameter: pointer): integer的函式(翻譯成Delphi的格式)。但是我們都知道,在Delphi中執行緒被封裝成一個TThread類。為什麼Delphi要將它封裝成一個類?Delphi是如何封裝的呢?我們怎樣才能充分的利用兩者的優點?這就是本下面要介紹的。

簡介

2.1 TThread的優點
將執行緒作為類來封裝有著許多優點。首先它能清晰、安全的界限執行緒相關的局部變數和進程相關的全局變數。類——對象的模型到實體的映射關係保證了聲明在類中的任何變數都是局部的,聲明在類外的任何變數都是全局的。所以在寫新執行緒的Execute函式只要注意對類外部的變數、方法的訪問就可以了,至於類內部的變數、方法則可以任意使用而不用考慮同步的問題。將執行緒封裝成類的更重要的好處是寫新執行緒的時候可以充分利用類的優點。你可以通過繼承來重用父類的功能,這實在是一個激動人心的功能。
2.2 TThread的封裝運行機理
既然已經知道將執行緒封裝成類有諸多好處,作為一個稱職的程式設計師一定會去了解Delphi是如何將執行緒封裝成類的,有沒有更好的封裝的方法的。
Delphi5中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在構造的時候執行緒是否第一次創建,如果是就創建執行緒窗體,然後增加執行緒計數,最後才建立執行緒實例。同理,TThread對象在銷毀的時候,先減少執行緒計數,然後判斷計數是否為0,如果是就銷毀執行緒窗體。
為什麼要建立一個執行緒窗體呢?答案就是TThread中的同步函式Synchronize()的需要。執行緒對象存取其他VCL的屬性時與其他執行緒的同步機制是通過訊息佇列來實現的。當執行緒函式執行Synchronize()時,他就向執行緒窗體傳送一條CM_EXECPROC訊息。因為執行緒窗體是進程的一個窗體(雖然它不可見),所以發向執行緒窗體的訊息都會進入進程訊息佇列,而訊息佇列的串列處理的特性保證不會出現訪問衝突。這是一個簡單而有效的解決方案。我不知道有沒有人在控制台程式中套用多執行緒,如果有的話,TThread類可能就不太適合了。這種情況下要么直接套用執行緒函式,要么自己寫一個新的TNewThread類了。
Delphi是在TThread類的外面聲明了一個局部函式ThreadProc。這個函式就是Windows SDK中介紹的執行緒函式,其聲明如下:
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沒有將執行緒函式作為TThread的一個成員函式,我想把ThreadProc放到TThread的Proctected段中TThread的靈活性可能會更好一點,不過現在的方法也不錯。可以看到ThreadProc以TThread對象作為Parameter參數。這樣可以保證TThread對象進入執行緒的堆疊中,一個TThread對象不破壞另一個同類型TThread對象的數據。當然,創建執行緒的執行緒還是可以訪問新執行緒中的數據的,Terminate過程就是這樣做的。所以TThread的數據還是可能被其他執行緒破壞的。所以外部執行緒要訪問執行緒的數據要小心處理,Terminate()是一個比較典型的:外部執行緒只寫,內部執行緒唯讀就能很好的工作,如果兩個執行緒都又讀又寫就可能導致邏輯混亂。
TThread類在構造執行緒實例是沒有直接調用CreateThread() API函式,而是使用了一個beginthread()函式。不知是什麼原因,該函式並沒有相應的Delphi Help文檔,只是在“TThreadFunc type”的介紹中一筆帶過。可能是Borland認為它的參數在以後還會修改吧。不過該函式和CreateThread() API的參數是一模一樣的。這是一個讓人興奮的地方,因為BeginThread()加入了Windows API沒有的異常處理功能。有意思的是,Delphi在BeginThread()由創建了一個新的執行緒函式,而把原來的執行緒函式和參數打包成TThreadRec作為新函式的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 //調用原來的執行緒函式
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參數分別是nil和0,使BeginThread()在調用CreateThread()時使用了預設的安全設定和默認堆疊大小。有關這兩個參數代表什麼意義請查閱Windows SDK文檔。

相關詞條

相關搜尋

熱門詞條

聯絡我們