?? chapt15.htm
字號:
<html><head><title>第十五章 多線程編程</title><meta http-equiv="Content-Type" content="text/html; charset=gb2312"></head><body bgcolor="#00000" text="#00cc66"><p align="center"><b><font color="#FF6666" size="4">第十五章 多線程編程</font></b></p><p> 本章描述Windows 95與Windows NT的基于線程的多任務設計。<br> <b>15.1 線 程</b><br> 我們知道,Windows 95支持兩種形式的多任務。第一種類型是基于進程的機制,這也是Windows從一開始就支持的多處理類型。進程本質上是指正在執行著的程序,在基于進程的多任務環境下,兩個至多個進程可以并發地執行。第二種類型是基于線程(thread)的機制,基于線程的多任務對于多數Windows用戶和程序員而言是一個嶄新的概念,因為Windows 95以前的Windows版本不能支持線程的概念(Windows NT除外)。<br> 線程是指進程中的一個執行流。多個線程可以并發地運行于同一個進程中。<br> 在Windows 95/98/NT中,每一個進程擁有至少一個線程,允許同時執行兩個或多個線程。并由我們的程序控制它們。基于線程的多任務允許同一程序的不同部分(線程)并發地執行。這樣,程序員就能夠寫出非常高效的程序,因為程序員能夠定義執行線程并管理程序的執行方式,能夠完全地控制程序片段的執行。例如,可以在一個程序中指定一個線程執行文件排序工作,指定另外一個線程負責收集來自某個遠程資源的信息,指定又一線程完成用戶輸入的工作。因為處于多線程多任務環境,每一線程都能夠并發地執行,這樣就能充分地利用CPU時間。 <br> 通常,多線程處理使程序運行速度減慢,除非我們有多線程CPU,以及可以在處理器中分離線程的操作系統。<br> 基于線程的多任務使得同步功能特征顯得更為重要。既然多個線程(及進程)可以并發執行,那么必須適當地協調線程間的執行順序以使其能同步訪問共享資源與內存,從而使程序編寫起來顯得更加復雜。Windows 95增加了一個完整的子系統以支持同步機制,其中的一些關鍵特征將在本章后進一步地討論。<br> <b>15.2 線 程 類</b><br> 所有進程至少都擁有一個執行線程,為了討論方便,我們稱該執行線程為主線程。然而,在一個程序中,除了主線程之外,可能還創建有一個或更多的執行線程。通常,一個線程一旦創建即開始執行。因此,一個進程最開始作為一個執行線程而被啟動,以后它還可以創建更多的執行線程。通過這種機制,基于線程的多任務得以實現。<br> 編寫基于線程的程序,既可以使用Windows 95提供的Win32應用程序接口函數(API),也可以使用Delphi 5.0提供的線程對象。而使用Delphi 5.0的線程對象(TThread對象),則可使編寫線程程序變得簡單、高效。TThread對象提供了許多特性和方法(成員函數),你只要根據工作需要對這些函數或方法進行重寫,即可在程序中實現多線程機制。<br> Tthread類是TObject對象的直接派生類。與其它大多數Delphi類和構件不同的是,你不能在程序中直接使用該對象,而必須從Tthread類產生一個新的派生類,并對需要使用的方法進行重寫,以重載(Override)Tthread類的方法。下面對Tthread類的部分特性和方法進行討論(除非特別說明,所有討論的特性和方法都是Public):<br> <b>15.2.1 線程類特性</b><br> 線程類包括以下幾種特性:<br> 1.FreeOnTerminate特性:該特性為布爾類型,只能在運行時間使用。 該特性決定線程結束時,是由VCL自動消除(destroy)線程對象(值為True),還是你自己負責消除線程對象(值為False)。FreeOnTerminate缺省值為 False。<br> 2.Thandle特性:每一個執行線程都有一個句柄,Win32 API大多數線程函數都需要用到句柄。該特性就用于保存線程的句柄,通過讀取Thandle特性的值,可以獲得線程的句柄,從而可調用Win32 API函數來執行控制線程的操作。<br> 3.Priority特性:該特性動態設置線程調度的優先級,只能在運行時間使用。優先級的缺省值為tpNormal,但你可以根據需要進行設置,以改變線程調度的優先級。詳細介紹請看12.3節。<br> 4.ReturnValue特性:該特性為整數類型,且只能由線程對象的派生類調用或訪問(Protected特性)。該特性等價于函數的Result變量,你可以使用該特性來指示線程執行的成功或失敗。<br> 5.Suspended特性:該特性為布爾類型,用于決定線程是否掛起。被掛起的線程停止執行,直到該線程被恢復。設置線程的Suspended特性為TRUE,可以掛起一個線程;設置線程的Suspended特性為FALSE,可以恢復一個線程的執行。<br> 6.Terminated特性:該特性為布爾類型,且為Protected特性,只讀,只能由線程對象的派生類調用或訪問。該特性決定線程是否應該中止執行。如果該特性值為TRUE,表示線程執行即將結束,這時程序應立即退出線程的Execute方法。你也可以通過調用Terminate方法來強行設置線程的Terminated特性為TRUE,從而達到退出線程執行的目的。<br> 7.ThreadID特性: 該特性用于保存線程的標識,有些Win32 API線程函數需要使用該標識。通過讀取ThreadID特性的值,可以獲得線程的標識,從而可調用Win32 API函數來執行其它控制線程的操作。<br> <b>15.2.2 線程類方法</b><br> 線程類主要包括以下幾種方法:<br> 1.Create構造函數:參數CreateSuspended用來選擇是馬上啟動線程(值為False)還是暫停它(值為True)。<br> 2.DoTerminate過程:該過程只能由線程對象內部方法調用,用于與主VCL線程的同步,并產生OnTerminate事件。一般來說,當線程終止時,線程會自動調用DoTermiante過程,程序員一般不需要重寫該過程的代碼。<br> 3.Execute過程:該方法開始一個線程的執行,你必須在派生的線程類中重寫該過程,以實現線程的功能。當使用Windows API時,這段代碼應該放置在線程函數中。Execute方法返回時,終止線程的執行,釋放線程堆棧,并調用OnTerminate事件驅動程序(如果有的話)。Execute方法必須周期性地檢測Terminated特性,如果為TRUE,Execute方法必須立即返回。如果線程返回失敗,說明調用Termianted方法時,線程沒有終止。<br> 4.Resume過程:該方法恢復一個被掛起的線程的執行。Resume方法調用可以嵌套,如果你執行了多次Suspend,則線程實際恢復運行前,你必須執行同樣次數的Resume調用。<br> 5.Suspend過程:該方法暫停一個線程的執行。暫停執行的線程可以調用Resume方法來恢復執行。Suspend方法調用可以嵌套,如果你執行了多次Suspend,則線程實際恢復運行前,你必須執行同樣次數的Resume調用。<br> 6.Synchronize過程:在Delphi 5.0的多線程編程中,各種VCL構件都是臨界資源,只能由主線程使用。其它線程要使用這些VCL構件,必須使用Synchronize方法,通過傳遞使用了VCL構件的方法,就可避免多線程與VCL構件的沖突,避免重入問題。該過程帶有唯一一個TThreadMethod類型的參數是一個不接收參數的對象方法,用于指定在線程對象中的方法。<br> 7.Terminate過程:該方法設置Terminated特性為TRUE,并通知你的線程,線程執行將終止。Execute方法必須周期性地檢測Terminated特性,如果為TRUE,必須立即返回。<br> 8.WaitFor函數:該方法等待線程執行的終止,然后返回ReturnValue特性的值(整型)。如果線程不終止,WaitFor函數就不會返回,因此,在調用WaitFor函數后,必須確保線程退出,這可以通過終止Execute方法的執行或當Terminated特性值為TRUE時退出來實現。另外,如果線程使用了Synchronize方法,則不要在主線程中使用WaitFor方法,因為這樣一來易引起死鎖,或導致各種Ethread例外的發生。Synchronize在主線程允許同步的方法執行之前一直等待,直到主線程進入消息循環,如果主線程調用了WaitFor,它就不能進入消息循環,Synchronize也不會返回,TThread會檢查到這種錯誤,從而引起Ethread例外。如果WaitFor已被調用,而Synchronize又一直等待,則TThread不會檢查到這種錯誤,你的程序將進入死鎖。<br> 9.OnTerminate過程:該過程是OnTerminate事件的驅動程序。OnTerminate事件發生在線程的Execute方法已經返回,TThread結束線程之前。該事件驅動程序只能在主線程使用,可以調用各種VCL方法和特性。<br> 15.3 創建多線程程序 <br> 下面以一個例子說明創建多線程執行程序的過程。在圖15.1中,程序將創建兩個執行5000次的循環的線程。每個線程在執行循環的每一次疊代后,將顯示循環的次數。當運行該程序時,會發現兩個線程是并發執行的。<br> <b>15.3.1 創建多線程</b><br> 在Delphi中,你即可以通過直接書寫線程代碼來創建線程,也可以Delphi的File|New命令向當前項目文件加入一個線程對象來實現。兩者結果都一樣,都是產生一個Tthread類的派生類。下面就使用后一種方法進行說明。<br> 選擇File菜單的New命令,打開New Items對話框,選擇New頁標簽下的Thread Object圖標(見圖15.2),打開New Thread Object對話框,在New Thread Object對話框的Class Name編輯框,輸入創建線程的類名,然后選擇OK按鈕,則Delphi生成一個新的代碼文件,該代碼文件即為新線程類的代碼文件。<br> 程序如下:<br> unit Unit2;<br> interface<br> uses Classes;<br> type <br> MyThread = class(TThread) <br> private<br> { Private declarations } <br> protected procedure Execute;<br> override;<br> end;<br> implementation<br> { Important: Methods and properties of objects in VCL can only be used <br> in a method called using Synchronize, for example, <br> Synchronize(UpdateCaption); and UpdateCaption could look like, <br> procedure MyThread.UpdateCaption; <br> begin <br> Form1.Caption := 'Updated in a thread';<br> end; }<br> { MyThread }<br> procedure <br> MyThread.Execute;<br> begin <br> { Place thread code here }<br> end;<br> end.<br> 該代碼文件生成一個線程類的派生類,并提供了需要派生類覆蓋的方法Execute說明。文件中還包含如何編寫處理VCL構件方法的說明信息。現根據本程序的需要,修改代碼文件,并將其保存為ThreadUnit.Pas。<br> interface<br> uses Classes, StdCtrls,SysUtils;<br> type <br> MyThread = class(TThread) <br> private <br> { Private declarations } <br> AEdit:TEdit; MaxLoop:Integer; CurrentLoop: Integer; <br> protected <br> procedure Execute; override;<br> procedure DisLoop; <br> public <br> constructor Create(Edit:TEdit;Max:Integer);<br> end;<br> implementation<br> { MyThread }<br> constructor MyThread.Create(Edit:TEdit;Max:Integer);<br> begin <br> inherited Create(False);<br> AEdit:=Edit; <br> MaxLoop:=Max;<br> FreeOnTerminate := True;<br> end;<br> procedure MyThread.DisLoop;<br> begin <br> AEdit.text:=InttoStr(CurrentLoop);<br> end;<br> procedure MyThread.Execute;<br> var I:Integer;<br> begin <br> for I:=0 to MaxLoop Do <br> begin <br> CurrentLoop:=I; <br> Synchronize(DisLoop); <br> if Terminated then Exit; <br> end;<br> end;<br> end. <br> 下面就有關問題進行說明:<br> 1.線程類中增加的AEdit,MaxLoop,CurrentLoop三個用于控制循環次數的顯示,由于每個線程的這三個特性都不相同,且都需要訪問VCL構件,因此它們都設置為私有特性,不能被繼承,也不能在線程類外使用,當類實例化時后,這些特性的值由Create拷入并初始化。 <br> 2.增加的DisLoop方法用于向編輯框構件寫入循環次數。Execute方法通過使用Synchronize調用DisLoop,從而解決了使用VCL構件時多線沖突問題。<br> 3.構造函數Create覆蓋了原線程類的構造函數,并將傳入的參數Edit(對應的編輯框件)和Max(最大循環次數)賦給實例化的對象。<br> 4.線程一旦創建,就執行Execute方法,每循環一次,就將循環次數顯示在編輯框.<br> <b>15.3.2 啟動線程</b><br> 本程序中,用戶通過單擊Start按鈕來開始兩個線程的執行,下面是單擊Start按鈕的事件驅動程序:<br> procedure TParaComupte.StartButtonClick(Sender: TObject);<br> begin <br> ThreadsRunning:=2; <br> MyThread1:=MyThread.Create(Edit1,5000); <br> MyThread1.OnTerminate := ThreadDone; <br> MyThread2:=MyThread.Create(Edit2,5000); <br> MyThread2.OnTerminate := ThreadDone; <br> StartButton.Enabled := False;<br> end;<br> 該事件驅動程序創建兩個線程,并傳入有關參數,ThreadsRunning全局變量用于說明創建線程的個數,ThreadDonee用于定義當線程執行結束時(發生OnTerminate事件)執行的過程,其代碼為:<br> procedure TParaCompute.ThreadDone(Sender: TObject);<br> begin <br> dec(ThreadsRunning); <br> if ThreadsRunning=0 then <br> StartButton.Enabled := True;<br> end;<br> 每結束一個線程,ThreadsRunning就減1,當ThreadsRunning值為0時,表示線程全部執行完畢,Start按鈕可以接收用戶輸入。<br> <b>15.3.3 線程的暫停、恢復與終止</b><br> 為了說明問題,本節在窗體中增加下列按鈕構件Suspend、Resume、Terminate(見圖15.4)。Suspend暫停線程1的執行,Resume恢復暫停了的線程,Terminate則終止線程的執行。<br> 在線程類一節,我們介紹了Suspended特性。該特性為布爾類型,用于決定線程是否掛起。設置線程的Suspended特性為TRUE,可以掛起一個線程;設置線程的Suspended特性為FALSE,可以恢復一個線程的執行。而執行Terminate過程,則可設置Terminated特性為TRUE,并通知你的線程,線程執行將終止。<br> 如MyThread1.Suspended:=True;<br> <b>15.4 線程的優先級</b><br> 每一個線程與一個優先級相聯。線程優先級用于確定一個線程收到多少CPU時間。線程類的Priority特性用于獲取和設置線程調度的優先級。優先級的缺省值為tpNormal,但你可以根據需要進行設置,以改變線程調度的優先級。Delphi的TThreadPriority枚舉類型定義了Priority特性所有可能的值。這些值包括:<br>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -