C++builder 中的多執行緒
在C++Builer中多執行緒的實現
還在Dos時代,人們就在尋求一種多工的實現。於是出現了TSR型別的後臺駐留程式,比較有代表性的有Side Kick、Vsafe等優秀的TSR程式,這類程式的出現和應用確實給使用者使用計算機帶來了極大的方便,比如Side Kick,我們程式設計可以在不用進編輯程式的狀態下,一邊編輯源程式,一邊編譯執行,非常方便。但是,Dos單任務作業系統的致命缺陷註定了在Dos下不可能開發出真正的多工程式。進入Windows3.1時代,這種情況依然沒有根本的改變,一次應用只能做一件事。比如資料庫查詢,除非應用編得很好,在查詢期間整個系統將不響應使用者的輸入。
進入了Windows NT和Windows 9x時代,情況就有了徹底的改觀,作業系統從真正意義上實現了多工(嚴格地說,Win9x還算不上)。一個應用程式,在需要的時候可以有許多個執行執行緒,每個執行緒就是一個小的執行程式,作業系統自動使各個執行緒共享CPU資源,確保任一執行緒都不能使系統死鎖。這樣,在程式設計的時候,可以把費時間的任務移到後臺,在前臺用另一個執行緒接受使用者的輸入。對那些對實時性要求比較高的程式設計任務,如網路客戶服務、序列通訊等應用時,多執行緒的實現無疑大大地增強了程式的可用性和穩固性。
在Windows NT和Windows 9x中,多執行緒的程式設計實現需要呼叫一系列的API函式,如CreateThread、ResumeThread等,比較麻煩而且容易出錯。我們使用Inprise公司的新一代RAD開發工具C++Builder,可以方便地實現多執行緒的程式設計。與老牌RAD工具Visual Basic和Delphi比,C++Builer不僅功能非常強大,而且它的程式語言是C++,對於系統開發語言是C的Windows系列作業系統,它具有其它程式語言無可比擬的優勢。利用C++Builder提供的TThread物件,多執行緒的程式設計變得非常簡便易用。那麼,如何實現呢?且待我慢慢道來,讓你體會一下多執行緒的強大功能。
1. 建立多執行緒程式:
首先,先介紹一下實現多執行緒的具體步驟。在C++Builder中雖然用Tthread物件說明了執行緒的概念,但是Tthread物件本身並不完整,需要在TThread下新建其子類,並重載Execute方法來使用執行緒物件。在C++Builder下可以很方便地實現這一點。
在C++Builder IDE環境下選擇選單File|New,在New欄中選中Thread Object,按OK,接下來彈出輸入框,輸入TThread物件子類的名字MyThread,這樣C++Builder自動為你建立了一個名為TMyThread的TThread子類。同時編輯器中多了一個名為Unit2.cpp的單元,這就是我們建立的TMyThread子類的原碼,如下:
#include
#pragma hdrstop
#include “Unit2.h”
#pragma package(smart_init)
//---------------------
// Important: Methods and properties of objects in VCL can only be
// used in a method called using Synchronize, for example:
//
// Synchronize(UpdateCaption);
//
// where UpdateCaption could look like:
//
// void __fastcall MyThread::UpdateCaption()
// {
// Form1-> Caption = “Updated in a thread”;
// }
//--------------------
__fastcall MyThread::MyThread(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//--------------------
void __fastcall MyThread::Execute()
{
//---- Place thread code here ----
}
//---------------------
其中的Execute()函式就是我們要線上程中實現的任務的程式碼所在處。在原始碼中包含Unit2.cpp,這個由我們建立的TMyThread物件就可以使用了。使用時,動態建立一個TMyThread 物件,在建構函式中使用Resume()方法,那麼程式中就增加了一個新的我們自己定義的執行緒TMyThread,具體執行的程式碼就是Execute()方法過載的程式碼。要載入更多的執行緒,沒關係,只要繼續建立需要數量的TMyThread 物件就成。
以上我們初步地實現了在程式中建立一個自定義的執行緒,並使程式實現了多執行緒應用。但是,多執行緒應用的實現,並不是一件簡單的工作,還需要考慮很多使多個執行緒能在系統中共存、互不影響的因素。比如,程式中公共變數的訪問、資源的分配,如果處理不當,不僅執行緒會死鎖陷入混亂,甚至可能會造成系統崩潰。總的來講,在多執行緒程式設計中要注意共享物件和資料的處理,不能忽視。因此,下面我們要講的就是多執行緒中常見問題:
2. 多執行緒中VCL物件的使用
我們都知道,C++Builder程式設計是建立在VCL類庫的基礎上的。在程式中經常需要訪問VCL物件的屬性和方法。不幸的是,VCL類庫並不保證其中物件的屬性和方法是執行緒訪問安全的(Thread_safe),訪問VCL物件的屬性或呼叫其方法可能會訪問到不被別的執行緒所保護的記憶體區域而產生錯誤。因此,TThread物件提供了一個Synchronize方法,當需要線上程中訪問VCL物件屬性或呼叫方法時,通過Synchronize方法來訪問屬性或呼叫方法就能避免衝突,使各個執行緒之間協調而不會產生意外的錯誤。如下所示:
void __fastcall TMyThread::PushTheButton(void)
{
Button1-> Click();
}
void __fastcall TMyThread::Execute()
{
...
Synchronize((TThreadMethod)PushTheButton);
...
}
對Button1-〉Click()方法的呼叫就是通過Synchronize()方法來實現的,它可以自動避免發生多執行緒訪問衝突。在C++Builder中,雖然有一些VCL物件也是執行緒訪問安全的(如TFont、TPen、TBrush等),可以不用Sychronize()方法對它們的屬性方法進行訪問呼叫以提高程式效能,但是,對於更多的無法確定的VCL物件,還是強烈建議使用Synchronize()方法確保程式的可靠性。
3. 多執行緒中公共資料的使用
程式設計中難免要在多個執行緒中共享資料或者物件。為了避免在多執行緒中因為同時訪問了公共資料塊而造成災難性的後果,我們需要對公共資料塊進行保護,直到一個執行緒對它的訪問結束為止。這可以通過臨界區域(Critical Section)的使用來實現,所幸的是在C++Builder中,給我們提供了一個TCriticalSection物件來進行臨界區域的劃定。該物件有兩個方法,Acquire()和Release()。它設定的臨界區域可以保證一次只有一個執行緒對該區域進行訪問。如下例所示:
class MyThread : public TThread
{
...
private:
TCriticalSection pLockX;
int x;
float y;
...
};
void __fastcall MyThread::Execute()
{
...
pLockX-> Acquire();//Here pLockX is a Global CriticalSection variable.
x++;
y=sin(x);
pLockX-> Release();
...
}
這樣,對公共變數x,y的訪問就通過全域性TCriticalSection 物件保護起來,避免了多個執行緒同時訪問的衝突。
4. 多執行緒間的同步
當程式中多個執行緒同時執行,難免要遇到使用同一系統資源,或者一個執行緒的執行要依賴另一個執行緒的完成等等,這樣需要線上程間進行同步的問題。由於執行緒同時執行,無法從程式本身來決定執行的先後快慢,使得執行緒的同步看起來很難實現。所幸的是Windows系統是多工作業系統,系統核心為我們提供了事件(Event)、Mutex、訊號燈(semaphore)和計時器4種物件來控制執行緒間的同步。在C++Builder中,為我們提供了用於建立Event的TEvent 物件供我們使用。
當程式中一個執行緒的執行要等待一項特定的操作的完成而不是等待一個特定的執行緒完成時,我們就可以很方便地用TEvent物件來實現這個目標。首先建立一個全域性的TEvent物件作為所有執行緒可監測的標誌。當一個執行緒完成某項特定的操作時,呼叫TEvent物件的SetEvent()方法,這樣將設定這個標誌,其他的執行緒可以通過監測這個標誌獲知操作的完成。相反,要取消這個標誌,可以呼叫ResetEvent()方法。在需要等待操作完成的執行緒中使用WaitFor()方法,將一直等待這個標誌被設定為止。注意WaitFor()方法的引數是等待標誌設定的時間,一般用INFINITE表示無限等待事件的發生,如果其它執行緒執行有誤,很容易使這個執行緒死住(等待一個永不發生的事件)。
其實直接用Windows API函式也可以很方便地實現事件(Event)、訊號燈(semaphore)控制技術。尤其是C++Builder,在呼叫Windows API方面有著其它語言無可比擬的優勢。所用的函式主要有:CreateSemaphore()、CreateEvent()、WaitForSingleObject()、ReleaseSemaphore()、SetEvent()等等,這裡就不贅述了。
本文結合Inprise(Borland)公司開發的強大的RAD工具C++Builder的程式設計,對Windows下的多執行緒程式設計作了比較全面的介紹。其實多執行緒的實現並不神祕,看了本文,你也可以編出自己的多執行緒程式,真正體會多工作業系統的威力。
附:本文是本人在使用C++Builder一年來的一些實踐體會。在完成自己的專案的同時,發現對多執行緒的程式設計一般的書籍都介紹得比較少,而實際應用中,多執行緒程式設計又是如此的重要,因此,本文通過對多執行緒程式設計比較全面的介紹,願能達到拋磚引玉之效。
最近在寫一個程式用到了多執行緒,所以對CB下的多執行緒有一定的學習。
現在把自己的一些心得講一下。水平有限,寫的很粗略,請大家見諒。
CB相對於VC來說,在CB下寫多執行緒程式是很簡單的。不僅是VCL中有TThread這個類。封裝了那些關於多執行緒的WINDOW API。我覺得更方便的是他提供了
直接訪問主VCL執行緒中物件的能力。可以很容易的和主執行緒中的窗體,控制元件
打交道。和單執行緒的方式沒有太多區別。只是在有多個執行緒都要訪問主執行緒
中的物件(比如訪問同一個窗體上的StringGrid).只要用Thread的Synchronize方法來呼叫那段訪問主VCL執行緒的程式碼(具體請看幫助),我們就不用擔心訪問衝突的問題了。而且對於多執行緒的同步和互斥,CB也對WINDOW 程式設計中那些機制進行了封裝。比如對臨界區CriticalSection封裝為TCriticalSection.事件Event封裝為TEvent.這些類相當簡單好用。
下面就是我覺得比較重要的幾點,供大家參考.
1。TThread的WaitFor方法。是等待一個執行緒返回。其返回值在這個執行緒裡可以任意設定。以便在該執行緒返回的時候讓呼叫他的執行緒知道他的執行情況。
在TThread的 OnTerminate事件中做執行緒的清除工作。他不是執行緒執行的一部分。
而是主VCL執行緒的一部分。所以在其中不能訪問Thread的區域性變數(如 int __thread i)
你可以把清楚程式碼寫在這裡,不用管現在在EXCUTE()方法執行到了哪個地方。
這麼看起來有點類似於C++裡的 finally 塊的作用。
2。TEvent很重要。實現執行緒的同步。WaitFor(int Timeout)功能類似於
WINDOW API WaitforSingleObject().返回值包括:
其中引數Timeout可以設為INFINITE表示永久等待,但這樣,程式很容易死在這裡。
wrSignaled 該事件發生(成功返回).
wrTimeout 等待超時.
wrAbandoned 在該事件的超時期限到達前,該事件物件已經被毀滅了。.
wrError 在等待過程中有異常產生,要知道具體產生的錯誤要檢視 TEvent的LastError
屬性。
3 TCriticalSection
這個相當於WIN32程式設計中的臨界區。
在多執行緒程式設計中,多個執行緒需要訪問同一個公用變數的時候。
來保證訪問的正確性。對公用變數訪問的程式碼寫在Enter();和Leave()之間。
比如有個公用變數 Count;
以下程式碼 :
TCriticalSection * pSection=new TCriticalSection();
pSection-> Enter();
Count++;
pSection-> Leave();
delete p;
Enter()方法進入臨界區,對其中的公用變數加鎖。
Leave()方法離開臨界區,對其中的公用變數解鎖。
4.TMultiReadExclusiveWriteSynchronizer
用來處理類似於多個生產者和多個消費者的問題。這裡的消費者是指
對公用變數進行讀操作的執行緒。
生產者是對公用變數進行寫操作的執行緒。