精品一区二区三区影院在线午夜_天天躁日日躁狠狠躁AV麻豆_国产午夜福利短视频_中文字幕乱偷无码AV先锋蜜桃_久久精品一二区东京热_国产成人亚洲日韩欧美久久久,国产成人精品久久一区二区三区

Delphi中線(xiàn)程類(lèi)TThread實(shí)現多線(xiàn)程編程1---構造、析構……

參考:http://www.cnblogs.com/rogee/archive/2010/09/20/1832053.html

  Delphi中有一個(gè)線(xiàn)程類(lèi)TThread是用來(lái)實(shí)現多線(xiàn)程編程的,這個(gè)絕大多數的Delphi書(shū)籍都有講到,但是基本上都是對TThread類(lèi)的幾個(gè)成員作一簡(jiǎn)單介紹,再說(shuō)明一個(gè) Execute的實(shí)現和 Synchronize 的用法就完了。然而這并不是多線(xiàn)程編程的全部。

  線(xiàn)程本質(zhì)上是進(jìn)程中一段并發(fā)運行的代碼。一個(gè)進(jìn)程至少有一個(gè)線(xiàn)程,即所謂的主線(xiàn)程。同時(shí)還可以有多個(gè)子線(xiàn)程。當一個(gè)進(jìn)程中用到超過(guò)一個(gè)線(xiàn)程時(shí),就是所謂的“多線(xiàn)程”

  那么這個(gè)所謂的“一段代碼”是如何定義的呢?其實(shí)就是一個(gè)函數或過(guò)程(對Delphi而言)

  如果用Windows API來(lái)創(chuàng )建線(xiàn)程的話(huà),是通過(guò)一個(gè)叫做 CreateThread的API函數來(lái)實(shí)現的,它的定義是

HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes,    //線(xiàn)程屬性(用于在NT下進(jìn)行線(xiàn)程的安全屬性設置,在9X下無(wú)效)
    DWORD dwStackSize,    //堆棧大小
    LPTHREAD_START_ROUTINE lpStartAddress,    //起始地址
    LPVOLD lpParameter,    //參數
    DWORD dwCreationFlags,    //創(chuàng  )建標志(用于設置線(xiàn)程創(chuàng  )建時(shí)候的狀態(tài))
    LPDWORD lpThreadId    //線(xiàn)程ID
);

  最后返回線(xiàn)程 Handle。其中的起始地址就是線(xiàn)程函數的入口,直至線(xiàn)程函數結束,線(xiàn)程也就結束了

  因為CreateThread的參數很多,而且是Windows API,所以在 C Runtime Library里面提供了一個(gè)通用的線(xiàn)程函數(理論上是可以在任何支持線(xiàn)程的OS中使用):

unsigned long_beginthread(void(_USERENTRY *__start)(void*), unsigned __stksize, void * __arg);

  Delphi也提供了一個(gè)相同功能的類(lèi)似函數

function BeginThread(SecurityAttributes: Pointer;
                                StackSize: LongWord;
                                ThreadFunc: TThreadFunc;
                                Parameter: Pointer;
                                CreationFlags: LongWord;
                                var ThreadId: LongWord): Integer;

  這三個(gè)函數的功能是基本相同的,它們都是將線(xiàn)程函數中大代碼放到一個(gè)獨立的線(xiàn)程中執行。線(xiàn)程函數與一般函數最大的不同在于,線(xiàn)程函數一啟動(dòng),這三個(gè)線(xiàn)程啟動(dòng)函數就返回了,主線(xiàn)程繼續向下執行,而線(xiàn)程函數在一個(gè)獨立的線(xiàn)程中執行,它要執行多久,什么時(shí)候返回,主線(xiàn)程是不管也不知道的

  正常情況下,線(xiàn)程函數返回之后,線(xiàn)程就終止了。但是也有其他方式:

Windows API:

VOID ExitThread(DWORD dwExitCode);

C Runtime Library:

void _endthread(void);

Delphi Runtime Library:

procedure EndThread(ExitCode: Integer);

  

  為了記錄一些必要的線(xiàn)程數據(狀態(tài)/屬性等),OS會(huì )為線(xiàn)程創(chuàng )建一個(gè)內部Object,如在Windows中那個(gè)Handle 便是這個(gè)內部Object的 Handle,所以在線(xiàn)程結束的時(shí)候還應該釋放這個(gè)Object

  雖然說(shuō)用API 和 RTL(Runtime Library)已經(jīng)可以很方便地進(jìn)行多線(xiàn)程編程了,但是還是需要進(jìn)行較多的細節處理,為此 Delphi在Classes單元中對線(xiàn)程做了一個(gè)較好的封裝,這就是VCL的線(xiàn)程類(lèi): TThread

  使用這個(gè)類(lèi)也很簡(jiǎn)單,大多數的Delphi都有說(shuō),基本用法是:

1.先從TThread派生一個(gè)自己的線(xiàn)程類(lèi)(因為T(mén)Thread是一個(gè)抽象類(lèi),不能生成實(shí)例)

2.然后是Override抽象方法:Execute(這就是線(xiàn)程函數,也就是在線(xiàn)程中執行的代碼部分)

3.如果需要用到可視VCL對象,還需要通過(guò)Synchronize過(guò)程進(jìn)行

  

  本文接下來(lái)要討論的是TThread類(lèi)是如何對線(xiàn)程進(jìn)行封裝的,也就是深入探究一下TThread類(lèi)的實(shí)現。因為只有真正地了解它,才能更好的使用它

  下面是Delphi 7 中TThread類(lèi)的聲明(本文只討論 Windows平臺下的實(shí)現,所以去掉了所有有關(guān)Linux平臺部分的代碼)

TThread = class
private
  FHandle: THandle;
  FThreadID: THandle;
  FCreateSuspended: Boolean;
  FTerminated: Boolean;
  FSuspended: Boolean;
  FFreeOnTerminate: Boolean;
  FFinished: Boolean;
  FReturnValue: Integer;
  FOnTerminate: TNotifyEvent;
  FSynchronize: TSynchronizeRecord;
  FFatalException: TObject;
  procedure CallOnTerminate;
  class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload;
  function GetPriority: TThreadPriority;
  procedure SetPriority(Value: TThreadPriority);
  procedure SetSuspended(Value: Boolean);
protected
  procedure CheckThreadError(ErrCode: Integer); overload;
  procedure CheckThreadError(Success: Boolean); overload;
  procedure DoTerminate; virtual;
  procedure Execute; virtual; abstract;
  procedure Synchronize(Method: TThreadMethod); overload;
  property ReturnValue: Integer read FReturnValue write FReturnValue;
  property Terminated: Boolean read FTerminated;
public
  constructor Create(CreateSuspended: Boolean);
  destructor Destroy; override;
  procedure AfterConstruction; override;
  procedure Resume;
  procedure Suspend;
  procedure Terminate;
  function WaitFor: LongWord;
  class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;
  class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);
  property FatalException: TObject read FFatalException;
  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類(lèi)在Delphi的RTL里面算是比較簡(jiǎn)單的類(lèi),類(lèi)成員也不多,類(lèi)屬性都很簡(jiǎn)單明白,本文將只對幾個(gè)比較重要的類(lèi)成員方法和唯一的事件:OnTerminate做詳細分析

  首先是構造函數

constructor TThread.Create(CreateSuspended: Boolean);
begin
    inherited Create;
    AddThread;
    FSuspended:= CreateSuspended;
    FCreateSuspended:= CreateSusPended;
    FHandle:= BeginThread(nil, 0, @ThreadProc, Pointer(Self), Create_SUSPENDED, FThreadID);
    if FHandle = 0 then
        raise EThread.CreateRssFmt(@SThreadCreateError,[SysErrorMessage(GetLastError)]);

end;

  雖然這個(gè)構造函數沒(méi)有多少代碼,但是卻可以算是最重要的一個(gè)成員,因為線(xiàn)程就是在這里被創(chuàng )建的。

  在通過(guò)Inherited 調用TObject.Create之后,第一句就是調用一個(gè)過(guò)程:AddThread,其源碼如下

procedure AddThread;
begin
    InterlockedIncrement(ThreadCount);
end;

  同樣有對應的RemoveThread

procedure RemoveThread;
begin
    InterlockedDecrement(ThreadCount);
end;

  它們的功能很簡(jiǎn)單,就是通過(guò)增減一個(gè)全局變量來(lái)統計進(jìn)程中的線(xiàn)程數。只是這里用于增減變量的并不是常用的Inc/Dec過(guò)程,而是用InterlockedIncrement/InterlockedDecrement這對過(guò)程。,它們實(shí)現的功能完全一樣,都是對變量加一或減一。但是它們有一個(gè)最大的區別,那就是InterlockedIncrement/InterlockedDecrement是線(xiàn)程安全的。即它們在多線(xiàn)程下能保證執行結果的正確,而Inc/Dec不能。或者按操作系統理論中的術(shù)語(yǔ)來(lái)說(shuō),這是一對“原語(yǔ)”操作。以加一為例來(lái)說(shuō)明二者實(shí)現細節上的不同:

  一般而言,對內存數據加一的操作分解之后有三步:

1)從內存讀出數據

2)數據加一

3)存入內存

  現在假設在一個(gè)有兩個(gè)線(xiàn)程的進(jìn)程中用Inc進(jìn)行加一操作,可能出現下面這種情況:

1)線(xiàn)程A從內存中讀出數據(假設為3)

2)線(xiàn)程B也從內存中讀出數據(也是3)

3)線(xiàn)程A對數據加一(現在是4)

4)線(xiàn)程B對數據加一(現在也是4)

5)線(xiàn)程A將數據存入內存(現在內存中的數據是4)

6)線(xiàn)程B也將數據存入內存(現在的內存中的數據還是4,但是兩個(gè)線(xiàn)程都對它加了一,應該是5才對,所以這里出現了錯誤的結果

  而用InterlockedIncrement過(guò)程則沒(méi)有這個(gè)問(wèn)題,因為所謂“原語(yǔ)”是一種不可中斷的操作,即操作系統能保證在一個(gè)“原語(yǔ)”執行完畢之前不會(huì )進(jìn)行線(xiàn)程切換。所以在上面的例子中,只有當線(xiàn)程A執行完并將數據存入到內存之后,線(xiàn)程B才可以開(kāi)始從中取數并進(jìn)行加一操作,這就保證了即使是在多線(xiàn)程情況下,結果一定會(huì )使正確的。

  前面的那個(gè)例子也說(shuō)明一種“線(xiàn)程訪(fǎng)問(wèn)沖突”的情況,這也是為什么線(xiàn)程之間需要“同步”(Synchronize),關(guān)于這個(gè),在后面說(shuō)到同步的時(shí)候還會(huì )再詳細討論

  說(shuō)到同步,有一個(gè)題外話(huà):加拿大滑鐵盧大學(xué)的教授李明曾就Synchronize一詞在“線(xiàn)程同步”中被譯作“同步”提出過(guò)異議,個(gè)人認為他說(shuō)的其實(shí)很有道理。在中文中“同步”的意思是“同時(shí)發(fā)生”,而“線(xiàn)程同步”目的就是避免這種“同時(shí)發(fā)生”的事情。而在英文中,Synchronize的意思有兩個(gè):一個(gè)是傳統意義上的同步(To occur at the same time),另一個(gè)是“協(xié)調一致”(To operate in unison)。在“線(xiàn)程同步”中的Synchronize一詞應該是指后面一種意思,即“保證多個(gè)線(xiàn)程在訪(fǎng)問(wèn)同一數據時(shí),保持協(xié)調一致,避免出錯”。不過(guò)像這樣譯得不準的詞在IT業(yè)還有很多,既然已經(jīng)是約定俗成了,本文也將繼續沿用,只是在這里說(shuō)明一下,因為軟件開(kāi)發(fā)是一項細致的工作,該弄清楚的,絕不能含糊。

  扯遠了,回到TThread的構造函數上,接下來(lái)最重要的就是這句了

FHandle:= BeginThread(nil, 0, @ThreadProc(Self), Create_SUSPENDED, FThreadID);

  這里就用到了前面所說(shuō)的Delphi RTL函數BeginThread,它有很多參數,關(guān)鍵是第三、四兩個(gè)參數。第三個(gè)參數就是前面說(shuō)到的線(xiàn)程函數,即在線(xiàn)程中執行的代碼部分。第四個(gè)參數則是傳遞給線(xiàn)程函數的參數,這里就是創(chuàng )建的線(xiàn)程對象(即Self)。其他的參數中,第五個(gè)是用于設置線(xiàn)程在創(chuàng )建之后即掛起,不立即執行(啟動(dòng)線(xiàn)程的工作實(shí)在 AfterConstruction中根據 CtreateSuspended標識來(lái)決定的),第六個(gè)是返回線(xiàn)程ID

  現在來(lái)看 TThread的核心:線(xiàn)程函數ThreadProc。有意思的是這個(gè)線(xiàn)程類(lèi)的核心卻不是線(xiàn)程的成員,而是一個(gè)全局函數(因為BeginThread過(guò)程的參數約定只能用全局函數)。下面是它的代碼

fucntion ThreadProc(Thread: TThread): Integer;
var
    FreeThread: Boolean;
begin
    try
        if not Thread.Terminated then
            try
                Thread.Execute;
            except
                Thread.FFatalException:= AcquireExceptionObject;
            end;
    finally
        FreeThread:= Thread.FFreeOnTerminate;
        Result:= Thread.FReturnValue;
        Thread.DoTerminate;
        Thread.FFinished:= True;
        SignalSyncEvent;
        if FreeThread then
            Thread.Free;
        EndThread(Result);
    end;
end;

  雖然這里也沒(méi)有多少代碼,但卻是整個(gè)TThread中最重要的部分,因為這段代碼是真正在線(xiàn)程中執行的代碼。下面對代碼做逐行說(shuō)明:

  首先判斷線(xiàn)程類(lèi)的Terminated 標識,如果未被標志位終止,則調用線(xiàn)程類(lèi)的Execute方法執行線(xiàn)程代碼,因為T(mén)Thread是抽象類(lèi),Execute方法是抽象方法,所以本質(zhì)上是執行派生類(lèi)中的Execute代碼。

  所以說(shuō),Execute 就是線(xiàn)程類(lèi)中的線(xiàn)程函數,所有在Execute 中的代碼都需要被當做線(xiàn)程代碼來(lái)考慮。如防止訪(fǎng)問(wèn)沖突等。如果Execute發(fā)生異常,則通過(guò)AcquireExceptionObject取得異常對象,并存入線(xiàn)程類(lèi)的FFatalException成員中

  最后是線(xiàn)程結束之前做的一些收尾工作。局部變量FreeThread記錄了線(xiàn)程類(lèi)的FreeOnTerminate屬性的設置,然后將線(xiàn)程返回值設置為線(xiàn)程類(lèi)的返回值屬性的值。然后執行線(xiàn)程類(lèi)的 DoTerminate方法

  DoTerminate方法的代碼如下

procedure TThread.DoTerminate;
begin
    if Assigned(FOnTerminate) then
        Synchronize(CallOnTerminate);
end;

  很簡(jiǎn)單,就是通過(guò)Synchronize 來(lái)調用 CallOnTerminate 方法,而CallOnTerminate 方法的代碼如下,就是簡(jiǎn)單地調用OnTerminate 事件:

procedure TThread.CallOnTerminate;
begin
    if Assigned(FOnTerminate) then
        FOnTerminate(Self);
end;

  因為 OnTerminate事件是在 Synchronize 中執行的,所以本質(zhì)上它并不是線(xiàn)程代碼,而是主線(xiàn)程代碼(具體見(jiàn)后面對Synchronize的分析)

  執行完OnTerminate之后,將線(xiàn)程類(lèi)的 FFinished標志設置為T(mén)rue。接下來(lái)執行SignalSyncEvent過(guò)程,其代碼如下

procedure SignalSyncEvent
begin
    SetEvent(SyncEvent);
end;

  也很簡(jiǎn)單,就是設置一下一個(gè)全局變量Event:SyncEvent,關(guān)于Event 的使用,本文將在后文詳述,而SyncEvent 的用途將在 WaitFor過(guò)程中說(shuō)明

  然后根據 FreeThread中保存的FreeOnTerminate設置決定是否釋放線(xiàn)程類(lèi),在線(xiàn)程類(lèi)釋放時(shí),還有一些操作,詳見(jiàn)下面的析構函數實(shí)現。

  最后調用 EndThread 結束線(xiàn)程,返回線(xiàn)程返回值。至此,線(xiàn)程完全結束。

  說(shuō)完構造函數,再看析構函數

destructor TThread.Destory;
begin
    if (FThreadID <> 0) and not FFinished then
    begin
        Terminate;
        if FCreateSuspended then
            Resume;
        WaitFor;
    end;
    if FHandle <> 0 then
        CloseHandle(FHandle);
    inherited Destory;
    FFatalException.Free;
    RemoveThread;
end;

  在線(xiàn)程對象被釋放之前,首先要檢查線(xiàn)程是否還在執行中,如果線(xiàn)程還在執行中(線(xiàn)程ID不為0,并且線(xiàn)程結束標志未設置),則調用Terminate 過(guò)程結束線(xiàn)程。Terminate 過(guò)程知識簡(jiǎn)單地設置線(xiàn)程類(lèi)的 Terminated標志,如下面的代碼:

procedure TThread.Terminate;
begin
    FTerminated:= True;
end;

  所以線(xiàn)程仍然必須繼續執行到正常結束之后才行,而不是立即終止線(xiàn)程,這一點(diǎn)要注意

  在這里說(shuō)一些題外話(huà):很多人問(wèn)過(guò)我,如何才能“立即”終止線(xiàn)程(當前是指用TThread 創(chuàng )建的線(xiàn)程)。結果當然是不行!終止線(xiàn)程的唯一的方法就是讓Execute 方法執行完畢,所以一般來(lái)說(shuō),要讓你的線(xiàn)程能盡快終止,必須在Execute 方法中在較短的時(shí)間內不斷檢查T(mén)erminated標志,以便能及時(shí)地退出。這是設計線(xiàn)程代碼的一個(gè)很重要的原則!

  當然如果你一定要能“立即”退出線(xiàn)程,那么TThread 類(lèi)不是一個(gè)好的選擇,因為如果用API強制終止線(xiàn)程的話(huà),最終會(huì )導致TThread 線(xiàn)程對象不能被正確釋放,在對象析構時(shí)出現 Access Violation。這種情況你只能使用API或者RTL函數來(lái)創(chuàng )建線(xiàn)程

  如果線(xiàn)程結束處于啟動(dòng)掛起狀態(tài),則線(xiàn)程轉入到運行狀態(tài),然后調用WaitFor進(jìn)行等待,其功能就是等待到線(xiàn)程結束后才繼續向下執行。關(guān)于WaitFor的實(shí)現,將放到后面說(shuō)明。

  線(xiàn)程結束后,關(guān)閉線(xiàn)程Handle(正常線(xiàn)程創(chuàng )建的情況下Handle都是存在的),釋放操作系統創(chuàng )建的線(xiàn)程對象。

  然后調用TObject.Destory 釋放本對象,并釋放已經(jīng)捕獲的異常對象,最后調用RemoveThread減少進(jìn)程的線(xiàn)程數

  其他關(guān)于 Suspend/Resume及線(xiàn)程優(yōu)先級設置等方面,不是本文的重點(diǎn),不再贅述。下面討論的是本文的另兩個(gè)重點(diǎn):Synchronize和WaitFor。具體的見(jiàn)下一篇博客

江山市| 买车| 安龙县| 新绛县| 北海市| 堆龙德庆县| 江陵县| 伊春市| 电白县| 东平县| 贡山| 乌兰察布市| 新巴尔虎右旗| 应城市| 个旧市| 龙南县| 朝阳区| 安宁市| 大方县| 霸州市| 昔阳县| 元朗区| 南漳县| 乐陵市| 阿图什市| 黄平县| 云浮市| 和政县| 台东市| 米林县| 兴城市| 内乡县| 镇平县| 来宾市| 芦山县| 武城县| 曲周县| 南充市| 嘉峪关市| 麦盖提县| 襄垣县|