WIN32下DELPHI中的多線程【深入VCL源碼】(一)

線程的基礎知識

線程的組成。線程有兩部分組成。

1、一個是線程的內核對象,操作系統用它來對線程實施管理。內核對象也是系統用來存放線程統計信息的地方。

2、另一個是線程堆棧,它用于維護線程在執行代碼時需要的所有函數參數和局部變量。

進程從來不執行任何東西,它只是線程的容器。線程總是在某個進程環境中創建的,而且它的整個壽命期都在該進程中。這意味著線程在它的進程地址空間中執行代碼,並且在進程的地址空間中對數據進行操作。因此,如果在單進程環境中,你有兩個或多個線程正在運行,那麽這兩個線程將共享單個地址空間。這些線程能夠執行相同的代碼,對相同的數據進行操作。這些線程還能共享內核對象句柄,因爲句柄表依賴于每個進程而不是每個線程存在。

線程是一種操作系統對象,它表示在進程中代碼的一條執行路徑。在每一個Wi n32的應用程序中都至少有一個線程,它通常被稱爲主線程或默認線程。在應用程序中也可以自由地創建別的線程去執行其他任務。線程技術使不同的代碼可以同時運行。當然,只有在多C P U的計算機上,多個線程才能夠真正地同時運行。在單個CPU上,由于操作系統把C P U的時間分成很短的片段分配給每個線程,這樣給人的感覺好像是多個線程真的同時運行,他們只是“看起來”同時在運行。

Win32是一種搶占式操作系統,操作系統負責管理哪個線程在什麽時候執行。如果當線程1暫停執行時,線程2才有機會獲得C P U時間,我們說線程1是搶占的。如果某個線程的代碼陷入死循環,這並不可怕,操作系統仍會安排時間給其他線程。

創建一個線程

注意:每個線程必須擁有一個進入點函數,線程從這個進入點開始運行。線程函數可以使用任何合法的名字。可以給線程函數傳遞單個參數,參數的含義由你自己定義。線程函數必須由一個返回值,它將成爲該線程的退出代碼。線程函數應該盡可能的使用函數參數和局部變量。線程函數類似下面的樣子(Object Pascal):

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
//注意最後的stdcall,後面我會描述一些有用的東西

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
function MyThread(info : Pointer):DWORD; stdcall;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
var

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
i : integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
begin

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
for i := 0 to Pinfo(info)^.count-1 do

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Form1.Canvas.TextOut(Pinfo(info)^.x,Pinfo(info)^.y,inttostr(i));

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Result := 0;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

上面的的代碼功能很簡單,你可以在程序中直接調用,例如這樣:

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
type

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Tinfo = record

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
count : integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
x : integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
y : integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Pinfo= ^Tinfo;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure TForm1.Button4Click(Sender: TObject);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
var

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ppi : Pinfo;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
begin

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ppi :=AllocMem(sizeof(tinfo));

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ppi^.count := 1000000;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ppi^.x := 100;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ppi^.y := 400;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
MyThread(ppi);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

當你在一個窗口中用這樣的方式調用時,你會發現在執行的過程中,你將無法在窗口上進行其他操作,因爲它工作于你程序的主線程之中。如果此時,你還希望窗口可以進行其他操作。怎麽辦?讓它在後台工作,讓它成爲另一個線程,使得不同的代碼可以同時運行。

做法很簡單,如果想要創建一個或多個輔助線程,只需要讓一個已經在運行的線程來調用CreateThread,原型如下:

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
HANDLE CreateThread(

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to thread security attributes

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
DWORD dwStackSize, // initial thread stack size, in bytes

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
LPVOID lpParameter, // argument for new thread

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
DWORD dwCreationFlags, // creation flags

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
LPDWORD lpThreadId // pointer to returned thread identifier

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
);

當CreateThread,被調用時,系統創建一個線程內核對象。該線程內核對象不是線程本身,而是操作系統用來管理線程的較小的數據結構。可以將線程內核對象視爲由關于線程的統計信息組成的一個小型數據結構。系統從進程的地址空間中分配內存,供線程的堆棧使用。新線程運行的進程環境與創建線程的環境相同。因此,新線程可以訪問進程的內核對象的所有句柄、進程中的所有內存和在這個相同的進程中的所有其他線程的堆棧。這使得單個進程中的多個線程確實能夠非常容易地互相通信。

下面來說這個函數的幾個參數:

1、psa 此參數是指向SECURITY_ATTRIBUTES結構的指針。如果想要該線程內核對象的默認安全屬性,可以(並且通常能夠)傳遞NULL。如果希望所有的子進程能夠繼承該線程對象的句柄,必須設定一個SECURITY_ATTRIBUTES結構,它的bInheritHandle(是否可繼承)成員被初始化爲True,關于SECURITY_ATTRIBUTES,因爲此文的目的不是介紹它,所以這裏不做詳細介紹,具體可以參考MSDN。通常使用,我們傳遞null就夠了。

2、cbStack 用于設定線程可以將多少地址空間用于它自己的堆棧。當調用CrateThread時,如果傳遞的值不是0,就能使該函數將所有的存儲器保留並分配給線程的堆棧。由于所有的存儲器預先作了分配,因此可以確保線程擁有指定容量的可用堆棧存儲器。通常狀況下,我們會設置爲0。

3、pfnStartAddr and pvParam,pfnStartAddr 參數用于指明想要新線程執行的線程函數的地址。線程函數的pvParam參數與原先傳遞給CreateThread的pvParam參數是相同的。CreateThread使用該參數不做別的事情,只是在線程啓動執行時將該參數傳遞給線程函數。該參數提供了一個將初始化值傳遞給線程函數的手段。該初始化數據既可以是數字值,也可以是指向包含其他信息的一個數據結構的指針。此時回頭再去看我上面例子上的MyThread,你會發現它由一個無類型的指針參數(用C來描述,應該是PVOID),在創建線程時,這個參數就通過pvParam來賦值。

4、fdwcreate 此參數可以設定用于控制創建線程的其他標志。它可以是兩個值中的一個。如果該值是0,那麽線程創建後可以立即進行調度。如果該值是CREATE_ SUSPENDED,系統可以完整地創建線程並對它進行初始化,但是要暫停該線程的運行,這樣它就無法進行調度。在DELPHI的WINDOWS.PAS單元,你可以發現它的定義

CREATE_SUSPENDED= $00000004;

5、pdwThreadId 最後一個參數必須是Dword的一個有效地址,CreateThread

使用這個地址來存放系統分配給新線程的ID.

有了上面這些基礎,下面我們就使用createThread來創建剛才那個MyThread線程(DELPHI7);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
//一個自定義類型

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
type

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Tinfo = record

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
count : integer;//計數器個數

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
x : integer;//要顯示在窗體上位置的橫座標

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
y : integer;//縱坐標

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Pinfo=^Tinfo;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
var

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
MyThreadHad : THandle;//一個全局變量,用來接受CreateThread創建新線程的句柄

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure TForm1.Button4Click(Sender: TObject);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
var

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ppi : Pinfo;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
MyThreadId : DWORD;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
begin

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{分配空間,注意,因爲這裏我只是一個用來演示CreateThread使用的代碼,所以沒有釋放pp,但優秀的代碼最後記得分配了空間一定要釋放}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ppi :=AllocMem(sizeof(tinfo));

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
//初始化

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ppi^.count := 100000;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ppi^.x := 100;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ppi^.y := 400;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
//下面這行代碼是關鍵

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
MyThreadHad := CreateThread(nil,0,@MyThread,ppi,0,MyThreadId);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

執行此段代碼,你會發現,它依然會在屏幕指定區域輸出文字,和最開始時我們用把MyThread在主線程中運行不同的是,此時,你依然可以對窗口進行其他操作。

看代碼的最後一行,它使用了createThread,看它的參數,第一個nil以及第二個0意外著,它使用默認的安全設置以及默認的線程堆棧大小,第三個參數是MyThread的地址(注意@符號),然後我們傳遞了ppi這個Pinfo類型的指針,使得線程函數接受一個參數,如果你不准備讓線程接受這個參數,用nil,fdwcreate參數,我們賦值爲0,意味著我們希望線程立即執行,最後一個參數用來接受新線程的ID。

讓我們來看看CreateThread都幹了些什麽。

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

上圖顯示了系統在創建線程和對線程進行初始化時必須做些什麽工作。調用CreateThread可使系統創建一個線程內核對象。該對象的初始使用計數是2(在線程停止運行和從CreateThread返回的句柄關閉之前,線程內核對象不會被撤消)。線程的內核對象的其他屬性也被初始化,暫停計數被設置爲1,退出代碼始終爲STILL_ACTIVE(0 x 1 0 3),該對象設置爲未通知狀態。

一旦內核對象創建完成,系統就分配用于線程的堆棧的內存。該內存是從進程的地址空間分配而來的,因爲線程並不擁有它自己的地址空間。然後系統將兩個值寫入新線程的堆棧的上端(線程堆棧總是從內存的高地址向低地址建立)。寫入堆棧的第一個值是傳遞給CreateThread的pvParam參數的值。緊靠它的下面是傳遞給CreateThread的pfnStartAddr參數的值。每個線程都有它自己的一組C P U寄存器,稱爲線程的上下文。該上下文反映了線程上次運行時該線程的CPU寄存器的狀態。線程的這組C P U寄存器保存在一個CONTEXT結構。CONTEXT結構本身則包含在線程的內核對象中。

指令指針和堆棧指針寄存器是線程上下文中兩個最重要的寄存器。線程總是在進程的上下文中運行的。因此,這些地址都用于標識擁有線程的進程地址空間中的內存。當線程的內核對象被初始化時,CONTEXT結構的堆棧指針寄存器被設置爲線程堆棧上用來放置pfnStartAddr的地址。當線程完全初始化後,系統就要查看CREATE_SUSPENDED標志是否已經傳遞給CreateThread。如果該標志沒有傳遞,系統便將線程的暫停計數遞減爲0,該線程可以調度到一個進程中。然後系統用上次保存在線程上下文中的值加載到實際的C P U寄存器中。這時線程就可以執行代碼,並對它的進程的地址空間中的數據進行操作。

在這裏,我還要簡單的描述一下CONTEXT結構,因爲WIN32是搶占式操作系統,一個線程幾乎不可能永遠的占據CPU,也就是說,它會在一定時間後(在WINDOWS中,大概式20ms的時間),被CPU放在一邊,一段時間之後,才可以重新獲得CPU時間片,此時就有一個問題,線程現在執行到了那裏,CPU在再次分配給它時間片執行的時候,必須知道這些信息,難道要從0開始嗎?CONTEXT結構的作用就是用來解決這個問題。

在Platform SDK中,你可以看到下面的信息:

“CONTEXT結構包含了特定處理器的寄存器數據。系統使用CONTEXT結構執行各種內部操作。目前,已經存在爲Intel、MIPS、Alpha和PowerPC處理器定義的CONTEXT結構。若要了解這些結構的定義,參見頭文件WinNT.h”。

該文檔並沒有說明該結構的成員,也沒有描述這些成員是誰,因爲這些成員要取決于Windows在哪個CPU上運行。實際上,在Windows定義的所有數據結構中,CONTEXT結構是特定于CPU的唯一數據結構。那麽CONTEXT結構中究竟存在哪些東西呢?它包含了主機C P U上的每個寄存器的數據結構。在x86計算機上,數據成員是Eax、Ebx、Ecx、Edx等等。如果是Alpha處理器,那麽數據成員包括IntV0、IntT0、IntT1、IntS0、In tRa和IntZero等等。

Windows實際上允許查看線程內核對象的內部情況,以便抓取它當前的一組CPU寄存器。若要進行這項操作,只需要調用GetThreadContext函數。關于此函數的使用,我們下次再說。

線程的終止

終止一個線程的運行,有4個方法:

1、線程函數返回,這是最好的

2、調用ExitThread函數,線程將自動撤銷

3、調用TerminateThread函數

4、包含線程的進程終止運行

線程函數返回

始終都應該將線程設計成這樣的形式,即當想要線程終止運行時,它們就能夠返回。這是確保所有線程資源被正確地清除的唯一辦法。如果

線程能夠返回,就可以確保下列事項的實現:

• 在線程函數中創建的所有C + +對象均將通過它們的撤消函數正確地撤消。

• 操作系統將正確地釋放線程堆棧使用的內存。

• 系統將線程的退出代碼(在線程的內核對象中維護)設置爲線程函數的返回值。

• 系統將遞減線程內核對象的使用計數。

調用Exitthread函數

void ExitThread(DWORD dwExitCode);

該函數將終止線程的運行,並導致操作系統清除該線程使用的所有操作系統資源。但是程序中用到的資源(例如DELPHI類對象)將不被撤消。

調用TerminateThread函數

Bool TerminateThread(HANDLE hThread,DWORD dwExitCode);

關産這個函數和ExitThread的區別,你會發現它除了有dwExitCode這個退出碼參數之外,還包含了可指定線程的句柄參數。看到這裏你就應該會想到兩者的區別,Exitthread總是撤消調用的線程,而TerminateThread能夠撤消任何線程。hThread參數用于標識被終止運行的線程的句柄。當線程終止運行時,它的退出代碼成爲你作爲dwExitCode參數傳遞的值。同時,線程的內核對象的使用計數也被遞減。值得注意的是,此函數是異步運行的函數,也就是說,它告訴系統你想要線程終止運行,但是,當函數返回時,不能保證線程被撤消。如果需要確切地知道該線程已經終止運行,必須調用WaitForSingleObject或者類似的函數,傳遞線程的句柄。

在進程終止時撤銷線程

這是很容易想到的。無須過多解釋。

線程終止時發生的操作

當線程終止運行時,會發生下列操作:

• 線程擁有的所有用戶對象均被釋放。在Windows中,大多數對象是由包含創建這些對象的線程的進程擁有的。但是一個線程擁有兩個用戶對象,即窗口和挂鈎。當線程終止運行時,系統會自動撤消任何窗口,並且卸載線程創建的或安裝的任何挂鈎。其他對象只有在擁有線程的進程終止運行時才被撤消。

• 線程的退出代碼從STILL_ACTIVE改爲傳遞給ExitThread或TerminateThread的代碼

• 線程內核對象的狀態變爲已通知。

• 如果線程是進程中最後一個活動線程,系統也將進程視爲已經終止運行。

• 線程內核對象的使用計數遞減1。當一個線程終止運行時,在與它相關聯的線程內核對象的所有未結束的引用關閉之前,該內核對象不會自動被釋放。

一旦線程不再運行,系統中就沒有別的線程能夠處理該線程的句柄。然而別的線程可以調用GetExitcodeThread來檢查由hThread標識的線程是否已經終止運行。如果它已經終止運行,則確定它的退出代碼.

BOOL GetExitcodeThread(HANDLE hThread,PDWORD pdwExitcode);

退出代碼的值在pdwExitcode);指向的DWORD中返回。如果調用GetExitcodeThread時線程尚未終止運行,該函數就用STILL_ACTIVE標識符(定義爲0x103)填入DWORD。如果該函數運行成功,便返回T R U E。

上面描述了結束線程的多種辦法,這裏必須說明一點,如果有可能,那盡量使用第一種方式來結束線程,它可以確保你釋放了所有的資源。好的程序應該盡可能的減少對客戶資源的浪費。

stdcall

准確的說,stdcall這個標示符本來和線程沒有直接的聯系,但因爲我這裏的示例代碼是用Object Pascal寫的,而我們調用的CreateThread則是用c實現的,這兩種語言的函數入棧的方式是不同的,pascal是從左到右。加上stdcall,可以使得入棧方式改爲從右到左以符合別的語言的習慣。我們上面調用createThread函數時,因爲我傳遞了那個無類型的指針參數,所以,必須加上stdcall指明入棧方式,否則會出現地址訪問錯誤。當然,如果你並不決定傳遞參數,你也可以不使用stdcall。不過作爲一種好的編碼習慣,你最好還是加上。

DELPHI中創建線程

如果你只想做一個代碼搬運工,你完全可以不了解上面的內容,但如果你想成爲一個合格的WIN32程序員,深入這些內容,比你膚淺的多學一門語言有用。

DELPHI把有關線程的API封裝在TThread這個Object Pascal的對象中。結合上面的內容,先去看TThread源碼

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
TThread = class

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
private

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$IFDEF MSWINDOWS}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FHandle: THandle;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FThreadID: THandle;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$ENDIF}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$IFDEF LINUX}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
// ** FThreadID is not THandle in Linux **

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FThreadID: Cardinal;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FCreateSuspendedSem: TSemaphore;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FInitialSuspendDone: Boolean;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$ENDIF}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FCreateSuspended: Boolean;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FTerminated: Boolean;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FSuspended: Boolean;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FFreeOnTerminate: Boolean;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FFinished: Boolean;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FReturnValue: Integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FOnTerminate: TNotifyEvent;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FSynchronize: TSynchronizeRecord;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FFatalException: TObject;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure CallOnTerminate;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$IFDEF MSWINDOWS}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
function GetPriority: TThreadPriority;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure SetPriority(Value: TThreadPriority);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$ENDIF}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$IFDEF LINUX}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
// ** Priority is an Integer value in Linux

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
function GetPriority: Integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure SetPriority(Value: Integer);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
function GetPolicy: Integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure SetPolicy(Value: Integer);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$ENDIF}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure SetSuspended(Value: Boolean);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
protected

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure CheckThreadError(ErrCode: Integer); overload;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure CheckThreadError(Success: Boolean); overload;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure DoTerminate; virtual;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure Execute; virtual; abstract;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure Synchronize(Method: TThreadMethod); overload;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property ReturnValue: Integer read FReturnValue write FReturnValue;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property Terminated: Boolean read FTerminated;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
public

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
constructor Create(CreateSuspended: Boolean);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
destructor Destroy; override;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure AfterConstruction; override;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure Resume;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure Suspend;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure Terminate;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
function WaitFor: LongWord;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property FatalException: TObject read FFatalException;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$IFDEF MSWINDOWS}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property Handle: THandle read FHandle;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property Priority: TThreadPriority read GetPriority write SetPriority;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$ENDIF}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$IFDEF LINUX}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
// ** Priority is an Integer **

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property Priority: Integer read GetPriority write SetPriority;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property Policy: Integer read GetPolicy write SetPolicy;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$ENDIF}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property Suspended: Boolean read FSuspended write SetSuspended;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$IFDEF MSWINDOWS}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property ThreadID: THandle read FThreadID;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$ENDIF}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$IFDEF LINUX}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
// ** ThreadId is Cardinal **

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property ThreadID: Cardinal read FThreadID;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{$ENDIF}

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

從TThread的聲明中可以看出,它定義了Windows和Linux下分別要完成的操作,這裏我們只談WIN32,TThread直接從TObject繼承,因爲,它不是組件。你還可以看到它有一個Execute的方法

procedure Execute; virtual; abstract;

並且你可以看到,它是抽象的,因爲,不能創建TThread的實例,你只能創建它的派生類的實例。再去看看它的構造函數,你會看到這樣一句代碼

FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);再深入去看這個BeginThread,

Result := CreateThread(SecurityAttributes, StackSize, @ThreadWrapper, P,CreationFlags, ThreadID);你看到了什麽?是的,CreateThread,結合這兩句,看看它都幹了些什麽,默認的安全屬性,默認的堆棧大小,一個入口地址,一個參數,一個創建標志,還有一個threadid。你和本文最開始的那些內容對上了嗎?我們又看到它傳遞的線程函數是ThreadProc,再去看看它。下面只帖了一些和本文有關系的代碼

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
try

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
if not Thread.Terminated then

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
try

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Thread.Execute;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
except

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Thread.FFatalException := AcquireExceptionObject;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
finally

它首先根據TThread類中的一個屬性Terminated(布爾類型)來判斷線程的狀態,如果你沒有通過外部代碼將Terminated甚至爲true,它將會執行Execute(注意這個方法,我們剛才提到過它是一個抽象的,你必須讓它幹點什麽,也就是說,Tthread.execute將是你的線程將要執行的操作)。然後是異常的處理。你是否對DELPHI的TThread有點了解了呢?如果有興趣,好好看看它的源碼吧。

說到這裏,DELPHI中TThread創建一個線程的基本流程就出來了。調用自己的構造函數,傳遞一個布爾類型的變量,這個變量對應CreateThread函數的fdwcreate參數,用來決定線程是立即執行還是挂起,構造函數又調用了一個BeginThread,而正是這個BeginThread調用了WIN API CreateThread,它將一個ThreadProc線程函數傳遞給CreateThread,而這個ThreadProc則調用你必須覆蓋的方法Execute來完成你想要進行的操作。

再來看看它的終止,繼續剛才的內容,看ThreadProc這個函數的下面代碼,你會發現,當Execute執行完畢之後,它就認爲這個線程終止了,它調用了EndThread(Result),然後這個EndThread又調用了ExitThread(ExitCode)。當結束使用TThread對象時,應該確保已經把這個Object Pascal對象從內存中清除了。這才能確保所有內存占有都釋放掉。盡管在進程終止時會自動清除所有的線程對象,但及時清除已不再用的對象,可以使內存的使用效率提高。還是ThreadProc的源碼,你會發現當線程的Execute執行完之後,它要根Thread.FFreeOnTerminate來決定是否釋放資源。FreeThread := Thread.FFreeOnTerminate;...if FreeThread then Thread.Free;這是非常好的,也就是說,你可以通過在對FreeOnTerminate這個屬性賦值爲true(觀察它的源碼,FreeOnTerminate是FFreeOnTerminate這個私有變量的訪問器),來讓TThread對象自動在線程執行完畢之後自動釋放資源。

看了這麽多,我們可以梳理一下思路了,使用TThread對象,我們必須從它派生一個類,然後你必須覆蓋Execute這個方法,在這裏,完成你要讓線程做的事情。如果有可能(或者說盡量,除非你對這個線程還有別的需求),還可以在這裏通過設置FreeOnTerminate := true,使得線程在執行完畢之後自動釋放資源。我們可以通過TThread對象構造函數的參數來決定線程是否立即運行。

一個例子:

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
//聲明一個線程,我們叫它TFrist

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Tfrist = class(TThread)

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
protected

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure Execute;override;//覆蓋Execute這個抽象的方法,這是你必須做的事情

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
var

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Form1: TForm1;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Ci : array[0..1000] of integer;//一個全局變量,我們將用TFrist來訪問它

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
WIN32下DELPHI中的多線程【深入VCL源碼】(一)
...{ Tfrist }

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure Tfrist.Execute;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
var

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
i : integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
begin

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
inherited;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
OnTerminate := Form1.ThreadDone;//注意一下這裏

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FreeOnTerminate := true;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
for i := 0 to 1000 do

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ci[i] := i;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure TForm1.Button1Click(Sender: TObject);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
begin

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
//初始化全局變量

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
FillMemory(@ci,1000,0);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Tfrist.Create(false);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure TForm1.ThreadDone(sender: TObject);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
var

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
i : integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
begin

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
for I := 0 to 1000 do

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ListBox1.Items.Add(IntToStr(ci[i]))

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

上面我省略了一些代碼,但大意已表。我們聲明了一個TFrist的類,它從TThread繼承而來,它將對一個全局變量的的數組CI進行初始化,並且將初始化的結果顯示在窗體的ListBox1上。

寫到這裏,你會發現上述代碼中的幾個“疑點”,其中一個我現在要說明的就是OnTerminate := Form1.ThreadDone;這一句,觀察ThreadDone的源碼,你會發現它其實就是完成將全局變量的內容顯示在窗體的LISTBOX中,這時,你可能會問,直接寫在線程裏,不可以嗎?爲什麽要這樣?原因很簡單。大多數V C L在被設計時,都只考慮了在任何時刻只有一個線程來訪問它。其局限性尤其體現在V C L的用戶界面部分。同時,一些非用戶界面部分也不是線程安全的。

1. 非用戶界面的V C L

實際上V C L只有很少的部分保證是線程安全的。可能在這很少的部分中,最讓人注意的是V C L的屬性流機制。V C L的流機制確保了組件流能被多線程安全地讀寫。請記住即使最基礎的V C L類(諸如TList),也不是爲安全地同時操作多個線程而設計的。對某些情況, V C L提供了一些線程安全的替代,比如,用TThreadList 來替代TList可以解決多個線程操作的問題。

2. 用戶界面的V C L

V C L要求所有的用戶界面控制要發生在一個應用程序的主線程的環境中(線程安全的TCanvas類除外)。當然,利用技術手段是可以有效地利用附屬線程更新用戶界面的(後面將會討論)。

對V C L的訪問只能在主線程中。這將意味著:所有需要與用戶打交道的代碼都只能在主線程的環境中執行。這是其結構上明顯的不足,並且這種需求看起來只局限在表面上,但它實際上有一些優點。首先,只有一個線程能夠訪問用戶界面,這減少了編程的複雜性。Win32要求每個創建窗口的線程都要使用GetMessage()建立自己的消息循環。正如你所想的,這樣的程序將會非常難于調試,因爲消息的來源實在太多了。其次,由于V C L只用一個線程來訪問它,那些用于把線程同步的代碼就可以省略了,從而改善了應用程序的性能。

那麽,如果有多個線程要訪問VCL,怎麽辦呢?有這麽幾個方法:

1、利用TThread的OnTerminate屬性,它是一個TNofityEvent類型,它指定的過程將在線程執行完畢之後運行,並且是運行在主線程環境中的,我上面的代碼就是使用了這種方法

2、利用TThread的Synchronize,

class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload;

它的作用是在主線程中執行一個方法,我們上面的例子,如果不用OnTerminate,那麽可以這麽改,

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Tfrist = class(TThread)

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
private

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure GetResut;//我們聲明了一個過程GetResutlt;它不包含任何參數

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
protected

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure Execute;override;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
//GetResut的實現部分

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure Tfrist.GetResut;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
var

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
i : integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
begin

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
for I := 0 to 1000 do

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Form1.ListBox1.Items.Add(IntToStr(ci[i]))

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
procedure Tfrist.Execute;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
var

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
i : integer;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
begin

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
inherited;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
OnTerminate := Form1.ThreadDone;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
// FreeOnTerminate := true;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
for i := 0 to 1000 do

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
ci[i] := i;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
//調用Synchronize

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
Synchronize(GetResut);

WIN32下DELPHI中的多線程【深入VCL源碼】(一)
end;

WIN32下DELPHI中的多線程【深入VCL源碼】(一)

3、利用通訊來完成。例如我們可以利用消息,看上面的Execute,在它的循環執行完畢之後,我們可以發送一個自定義消息,然後窗口處理這個消息。

參考文獻:

1、《delphi5開發人員指南》

2、《WINDOWS核心編程》

注:轉載請著名出處,謝謝!

未完待續!

 
深入VCL源碼研究DELPHI窗體的創建和關閉
一、窗體的建立 在DELPHI中,我們通常使用Application.CreateForm(TForm2, Form2)和TForm.create來創建窗體,我們幾乎無法區別這兩種方法差異,更何況,我們更多的時候都是在使用TForm.create來生成子窗體。 ...查看完整版>>深入VCL源碼研究DELPHI窗體的創建和關閉
 
如何將界面代碼和功能代碼分離(基于Delphi/VCL)
摘要:如何將界面代碼和功能代碼分離(基于Delphi/VCL)asp/sunidoc.asp">東日文檔 很多朋友看了上次我寫的“創建良好設計的代碼(基于Delphi/VCL)”後,對我說感覺上可以接受其中的觀點,但似乎說得太簡...查看完整版>>如何將界面代碼和功能代碼分離(基于Delphi/VCL)
 
《Delphi高手突破》節選--脫離VCL的Windows程序
我知道,朋友們等待這本書很久了,好多朋友都發Email來問。出版社現在說,10月中旬可以上市。上市最快的,應該是網絡上的銷售,然後,常規渠道來說,北京應該比其他區域快。在此,再貼出一段節選。書的支持網站:htt...查看完整版>>《Delphi高手突破》節選--脫離VCL的Windows程序
 
《Delphi高手突破》節選--脫離VCL的Windows程序
我知道,朋友們等待這本書很久了,好多朋友都發Email來問。出版社現在說,10月中旬可以上市。上市最快的,應該是網絡上的銷售,然後,常規渠道來說,北京應該比其他區域快。在此,再貼出一段節選。書的支持網站:光盤...查看完整版>>《Delphi高手突破》節選--脫離VCL的Windows程序
 
Delphi 6 新特性-VCL單元變化及特性(中文)
Delphi 6 新特性原著:Borland Corporation翻譯:Musicwind®開始日期:2001-07-13結束日期:2001-07-14 (完成VCL單元變化及特性) 聲明:以下文章的內容取自Delphi 6附帶的幫助文件。版權所有Borland Corporat...查看完整版>>Delphi 6 新特性-VCL單元變化及特性(中文)