天天看點

BCB---多線程

多線程程式設計是提高系統資源使用率的一種常見方式。它占用的資源更小,啟動更快,還可以實作在背景運作一些需時較長的操作。

一、初識TThread對象

VCL提供了用于多線程程式設計的TThread類,在這個類中封裝了Windows關于線程機制的Windows API,通常将它的執行個體成為線程對象。線程對象通過封裝簡化了多線程應用程式的編寫。注意,線程對象不允許控制線程堆棧的大小或安全屬性。若需要控制這些,必須使用Windows API的CreateThread()或BeginThread()函數。不過,即使是使用Windows Thread API函數建立和控制多線程,仍然可從一些同步線程對象或下節将要描述的方法中受益。

要在應用程式中使用線程對象,必須建立TThread的一個派生類。File|New|Thread Object,系統會提示為新線程對象提供類名,我們将其命名為TMyThread。我們必須自行在構造函數以及Execute()函數中添加代碼。自動生成的構造函數中有一個參數,如果為true的話線程建立後将進入挂起狀态,直到線程對象的Resume()函數被調用才開始執行。如果為false則線程建立後會立刻開始執行。

我們先建立一個執行個體來親自感受下多線程:在窗體上放兩個Button和兩個Edit元件,自動命名。然後File|New|Thread Object來建立一個線程對象,命名為TMyThread。

以下請看完整工程代碼:

//Unit1.h        //主窗體頭檔案

//---------------------------------------------------------------------------

#ifndef Unit1H

#define Unit1H

//---------------------------------------------------------------------------

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

#include "Unit2.h"

//---------------------------------------------------------------------------

class TForm1 : public TForm

{

__published:    // IDE-managed Components

    TButton *Button1;

    TButton *Button2;

    TEdit *Edit1;

    TEdit *Edit2;

    void __fastcall Button1Click(TObject *Sender);

    void __fastcall Button2Click(TObject *Sender);

    void __fastcall FormCreate(TObject *Sender);

private:    // User declarations

    TMyThread *thread1,*thread2;

public:        // User declarations

    __fastcall TForm1(TComponent* Owner);

};

//---------------------------------------------------------------------------

extern PACKAGE TForm1 *Form1;

//---------------------------------------------------------------------------

#endif

//Unit1.cpp        //主窗體實作檔案

//---------------------------------------------------------------------------

#include <vcl.h>

#pragma hdrstop

#include "Unit1.h"

//---------------------------------------------------------------------------

#pragma package(smart_init)

#pragma resource "*.dfm"

TForm1 *Form1;

//---------------------------------------------------------------------------

__fastcall TForm1::TForm1(TComponent* Owner)

    : TForm(Owner)

{

}

//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)

{

    thread1->Resume();    //單擊後才啟動線程

}

//---------------------------------------------------------------------------

void __fastcall TForm1::Button2Click(TObject *Sender)

{

    thread2->Resume();

}

//---------------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender)

{

    thread1=new TMyThread(true,Edit1);    //建立線程對象執行個體

    thread2=new TMyThread(true,Edit2);

}

//---------------------------------------------------------------------------

//Unit2.h        //線程類頭檔案

//---------------------------------------------------------------------------

#ifndef Unit2H

#define Unit2H

//---------------------------------------------------------------------------

#include <Classes.hpp>

//---------------------------------------------------------------------------

class TMyThread : public TThread

{           

private:

    TEdit *edResult;    //自定義局部變量

    String strResult;

protected:

    void __fastcall Execute();

    void __fastcall ShowResult();        //自定義函數

public:

    __fastcall TMyThread(bool CreateSuspended,TEdit *AEdit);    //注意:修改了預設參數

};

//---------------------------------------------------------------------------

#endif

//Unit2.cpp        //線程類實作檔案

//---------------------------------------------------------------------------

#include <vcl.h>

#pragma hdrstop

#include "Unit2.h"

#pragma package(smart_init)

//---------------------------------------------------------------------------

__fastcall TMyThread::TMyThread(bool CreateSuspended,TEdit *AEdit)

    : TThread(CreateSuspended)

{

    edResult=AEdit;

}

//---------------------------------------------------------------------------

void __fastcall TMyThread::Execute()

{

    for(int i=0;i<200;i++)

    {

        strResult=IntToStr(i);

        Synchronize(ShowResult);    //管理線程同步,保證安全性

        Sleep(100);

    }

}

//---------------------------------------------------------------------------

void __fastcall TMyThread::ShowResult()

{

    edResult->Text=strResult;

}

//---------------------------------------------------------------------------

然後我們F9運作程式就可以檢視效果了。

二、編寫線程函數

Execute()函數就是線程函數,它包含了程式中所有需要并行執行的代碼。除了共享相同的程序空間外,可以認為線程就是通過應用程式啟動的程式。但在編寫線程函數的時候需要注意與單獨程式的不同之處。因為線程與其他線程共享記憶體空間,是以必須确認沒有覆寫應用程式中其它線程的記憶體位址。而另一方面,可以使用共享記憶體線上程之間進行通信。

線上程函數内部,我們可以使用任意的全局變量,但有些變量我們并不希望同一線程類的其他執行個體共享它,就可以聲明一個線程(thread-local)變量。通過将__thread修飾語加入變量聲明就可以聲明一個線程變量。例如 int __thread x; 聲明一個整型變量。

__thread修飾語隻可用于全局(檔案範圍)或靜态變量。指針和函數變量不能作為線程變量。使用“在寫入時複制”文法的類,如AnsiStrings也不能作為線程變量。需要在運作時進行初始化或析構的類型也不能被聲明為__thread類型。

當程式中調用Resume()函數時,線程啟動并繼續執行直到Execute()結束。這就是線程執行特定任務,并在其完成時終止的模式。然而,有時應用程式需在一些外部條件滿足時終止線程。通過檢查Terminated屬性可允許其它線程通知本線程終止。當其它線程試圖終止本線程時,它調用Terminate()函數。Terminate()函數将本線程的Terminated屬性設定為true。Execute()函數通過檢查和響應Terminated屬性來實作Terminate()函數。下面的執行個體示範了這種做法:

void __fastcall TMyThread::Execute()

{

    while( !Terminated )

    {

    }

}

線上程函數終止時我們可能需要做一些清理工作。由于線上程終止前,OnTerminate事件會發生,是以我們可以将清理代碼放在OnTerminate事件處理程式中,這樣可確定不管Execute()函數如何執行,清理代碼總是可以被執行。要注意OnTerminate事件處理程式不作為線程的一部分運作,而是在應用程式的主線程中執行的。這意味着:

(1)在OnTerminate事件處理程式中不能使用任何線程局部變量。

(2)在OnTerminate事件處理程式中可安全地通路任何元件及VCL對象而不會和其他線程發生沖突。

三、協調線程

在編寫線程執行時運作的代碼時,必須考慮到可能同步執行的其他線程行為。主要有兩種情況:一個是避免兩個線程試圖同時使用某一個全局對象或變量;另一個是某線程中的一些代碼可能會依賴其他線程所執行任務的結果。

1,避免同時通路

為避免在通路全局對象或變量時與其他線程發生沖突,可能需要暫停其他線程的執行,直到該線程代碼完成操作。這裡需要注意,不要暫停其他不需停止的線程執行,這樣會使效率嚴重降低,也無法獲得使用多線程的優點。

    <1>,鎖定對象

    一些對象内置了鎖定功能,以防止其他線程使用該對象的執行個體。例如,畫布對象(TCanvas及其派生類)有一種Lock()函數可防止其他線程通路畫布,直到調用Unlock()函數。

    VCL還包含一種線程安全的清單對象TThreadList。調用TThreadList::LockList()傳回清單對象,同時組織其他線程使用清單直到調用UnlockList()函數。調用TCanvas::Lock()函數或TThreadList::LockList()函數時可以安全地嵌套。鎖定直到最後一個鎖定調用比對到同一線程中相應的解鎖調用時才會被釋放。

    顯然這種方法隻對部分類有效。

    <2>,使用重要區段

    若對象沒有提供内置的鎖定功能,可使用重要區段。重要區段像門一樣,每次隻允許一個線程進入。要使用它,需建立TCriticalSection的全局執行個體。TCriticalSection有兩個函數:Acquire()(阻止其他線程執行該區段)以Release()(取消對其他線程的阻止)。

    每個重要區段都與需要保護的全局記憶體關聯。每個要通路這個全局記憶體的線程首先要調用Acquire()函數以確定其他線程不能通路它。當線程結束時,要調用Release()函數以便其他線程能繼續通路。

    例如,應用程式有一個全局重要區段變量pLockXY,可阻止通路全局變量X和Y。任何使用X或Y的線程必須調用重要區段,如下所示:

    pLockXY->Acquire();

    try{

    Y=sin(X);

    }

    __finally

    {

    pLockXY->Release();

    }

    <3>,使用多重讀、獨占寫的同步器

    當使用重要區段來保護全局記憶體時,每次隻有一個線程可以使用該内容。這種保護可能超出了需要,特别是當有一個經常讀但很少寫的對象或變量時更是如此。多個線程同時讀相同記憶體但沒有線程寫記憶體是沒有危險的。當有一些經常被讀但很少有線程向其寫入的全局記憶體時,可使用TMultiReadExclusiveWriteSynchronizer對象保護它。這個對象與重要區段一樣,但它允許多個線程同時讀,隻要沒有線程寫即可。線程必須有獨占通路權才能寫使用TMultiReadExclusiveWriteSynchronizer保護的記憶體。

    要使用“多重讀、獨占寫”的同步器,需建立TMultiReadExclusiveWriteSynchronizer的一個全局執行個體,它與要保護的全局記憶體關聯。每個需要讀記憶體的線程首先要調用BeginRead()函數。它確定目前無其它線程寫記憶體。線程完成讀操作後調用EndRead()函數。任何線程要寫記憶體的時候必須先調用BeginWrite()函數,結束後調用EndWrite()函數。

    <4>,共享記憶體的其他技術

    當使用VCL對象時,使用主VCL線程來執行代碼,可確定對象不會間接地通路同時被其他線程中的VCL對象使用的記憶體。若全局變量不需要被多個線程共享,可使用線程變量來代替它。線程可以不需要等待或暫停其他線程。

2,等待其他線程

若線程必須等待另一線程完成某項任務,可讓線程臨時中斷執行。然後要麼等待另一線程完全執行結束,要麼等到另一線程通知完成了該項任務。

<1>,等待線程執行結束

要等待另一線程執行結束,使用它的WaitFor()函數。WaitFor()函數直到那個線程終止才傳回,終止的方式要麼完成了Execute()函數,要麼由于一個異常。例如,下面的代碼在通路清單中的對象前等待,直到另一線程填滿該清單。

void __fastcall TVisitList::Execute()

{

int fileRes;

TFillThread *fl=new TFillThread(false);

fillRes=f1->WaitFor();

//以下進行後續處理

}

上例中,清單對象隻在WaitFor()函數指出該清單被填滿時才能被通路。傳回值由被等待線程的Execute()函數指定。然而,因為調用WaitFor()函數的線程需要直到另一線程的執行結果,無法以代碼調用Execute()函數,Execute()函數也無法傳回任何值。是以TFillThread線程的Execute()函數應該設定ReturnValue屬性。ReturnValue通過被其他線程調用的WaitFor()函數傳回。傳回值是一個證書,由應用程式确定其含意。

<2>,等待任務完成

有時,隻需等待線程完成一些操作而不是執行結束。為此,可使用一個事件對象。事件對象(TEvent)應具有全局範圍以便他們能夠為所有線程可見。當一個線程完成一個被其他線程依賴的操作時,調用TEvent::SetEvent()函數。它發出一個信号,以便任何其他線程可檢查并得知操作完成。要關掉信号則使用ResetEvent()函數。

四、調試多線程程式

當調試多線程應用程式時,試圖跟蹤所有并行線程的狀态,或在斷點停止時判斷是哪一個線程的執行往往會使人感到迷惑。可使用Thread Status框來幫助跟蹤并控制應用程式中所有的線程。開啟Thread Status框的方法是:View|Debug Windows|Threads。

當一個調試事件(斷點、異常、暫停)發生時,線程狀态訓示各個線程的狀态。右擊可定位相應的源代碼位置或将其他線程設定為目前線程等。

最後要提醒的是,不要使用無意義的多線程。如果一段程式完全是串行的,每一步的操作都需要上一步的結果,那麼在這裡采用多線程技術就是毫無意義的。

繼續閱讀