BCB 多線程的同步與協調


多線程編程是提高系統資源利用率的一種常見方式。它占用的資源更小,啟動更快,還可以實現在后台運行一些需時較長的操作。[喝小酒的網摘]http://blog.hehehehehe.cn/a/8498.htm

一、初識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。

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

最后要提醒的是,不要使用無意義的多線程。如果一段程序完全是串行的,每一步的操作都需要上一步的結果,那么在這里采用多線程技術就是毫無意義的。[喝小酒的網摘]http://blog.hehehehehe.cn/a/8498.htm

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM