多線程的基本概念windows
(轉)
win 98/nt/2000/xp 是個多任務操做系統,也就是:一個進程能夠劃分爲多個線程,
每一個線程輪流佔用cpu 運行時間和資源,或者說,把cpu 時間劃成片,每一個片分給不一樣的線
程,這樣,每一個線程輪流的「掛起」和「喚醒」,因爲時間片很小,給人的感受是同時運行的。
多線程帶來以下好處:(本身閱讀)
1)避免瓶頸;
2)並行操做;
3)提升效率;
在多線程中,經過優先級管理,可使重要的程序優先操做,提升了任務管理的靈活性。
另外一方面,在多cpu 系統中,能夠把不一樣的線程在不一樣的cpu 中執行,真正作到同時處
理多任務(win 98 只是模擬的,而win/nt/2000是真正的多cpu同時操做)。
多線程的兩個概念:api
1) 進程:也稱任務,程序載入內存,並分配資源,稱爲「一個進程」。注意:進程本
身並不必定要正在執行。進程由如下幾部分組成:
a>一個私有的地址空間,它是進程可使用的一組虛擬內存地址空間;
b>程序的相關代碼、數據源;
c>系統資源,好比操做系統同步對象等;
d>至少包含一個線程(主線程);安全
2) 線程:是程序的執行單位(線程自己並不包括程序代碼,真正擁有代碼的是進程)
,每一個進程至少包括一個線程,稱爲主線程,一個進程若是有多個線程,就能夠共享同一進
程的資源,並能夠併發執行。
線程是進程的一個執行單元,是操做系統分配cpu 時間的基本實體,線程主要由以下兩
部分組成:
a>數據結構;
b>cpu 寄存器和堆棧;
一個進程中的線程,能夠獨立運行,也能夠控制另外一個線程的運行。數據結構
請注意:
多線程不能濫用,書上提到了多線程的幾個缺點(自閱)。多線程
1-2 tthread 對象
雖然windows 提供了比較多的多線程設計的api 函數,可是直接使用api 函數一方面極
其不方便,並且使用不當還容易出錯。爲解決這個問題,borland 公司率先推出了一種
tthread 對象,來解決多線程設計上的困難,簡化了多線程問題的處理。
應該注意,tthread 對象是沒有實例的,它和界面的交流,主要依靠主窗體(主vcl線程)
,這和其餘對象使用上有些區別。併發
1、tthread 對象的主要方法
構造線程:ide
constructor create(createsuspended:boolean)函數
其中:createsuspended=true 構造但不喚醒
false 構造的同時即喚醒操作系統
也能夠用以下方法.net
inheried create(createsuspended:boolean)
掛起線程:
suspend
(把線程掛起的次數加一)
喚醒線程:
resume
(注意:注意這個屬性是把線程掛起的次數減一,當次數爲0 時,即喚醒。也就是說,
線程掛起多少次,喚醒也須要多少次。同時掛起的時候將保持線程的地址指針不變,因此線
程掛起後再喚醒,將從掛起的地方開始運行)
析構(清除線程所佔用的內存):
destroy
終止線程(後面會具體討論):
terminate
2、線程應用的簡單例子:
下面經過一個例子說明上述方法的應用。咱們知道,循環是獨佔性最強的運行方式之一,
如今但願創建兩個線程對象,實現循環的並行運行。具體方法以下:
file---new---thread object
這就自動在主form中創建了一個線程單元(在對話框裏寫上線程名字),默認的名字
是unit2。一樣方法創建第二個線程單元unit3。
要注意的是:unit2和unit3中有一個給定的過程:
procedure object.execute;
begin
end;
其中的程序是線程喚醒後自動執行的程序,也能夠在裏面調用其餘自定義的過程和函數。
這個過程的結束,意味着線程程序的結束。
爲了構造線程,在interface的type區,定義一個構造過程:
type
object = class(tthread) //自動給出的,也能夠直接改
private
protected
procedure execute; override;
public
constructor create; //本身寫的
而且在implementation區域寫上:
constructor object.create;
begin
inherited create(true);
end
其中object 爲線程對象的名字。因此這麼寫,是但願在主form中調用這個構造過程。
create()的參數用true,代表構造出的線程爲掛起狀態。
注意一下,在同一個線程對象裏,若是兩次構造,將產生兩個獨立的線程,不但運行
是獨立的,並且使用線程的局部變量也是獨立的。但這裏爲了簡化問題,仍是創建了兩個獨
立的線程對象,並且兩個循環數是不一樣的,在並行運算時容易判斷出是兩個不一樣的程序在運行。
假定咱們給兩個線程對象起的名字是:
mymath1
mymath2
這樣在unit1,應該做以下聲明:
implementation
<$r *.dfm>
uses unit2,unit3;
var thread1:mymath1;
thread2:mymath2;
這樣在主線程,將能夠經過這兩個線程變量調用對應的線程方法。
在主線程區構造線程的方法是:
thread1:=mymath1.create;
thread2:=mymath2.create;
掛起:
thread1.suspend;
thread2.suspend;
喚醒:
thread1.resume;
thread2.resume;
析構:
thread1.destroy;
thread2.destroy;
這裏須要說明的是,因爲線程單元須要調用form的edit控件(對象),能夠採用兩種方法:
1) 在線程單元定義一個tedit對象,例如
edit4:tedit;
在execute過程內直接引用
但在unit1中必定要在formcreate過程裏做一個賦值:
procedure tform1.formcreate(sender: tobject);
begin
thread1.edit4:=edit1;
end;
這樣,就把第一線程的edit4與form上的edit1聯繫來。
2)在第二個線程中首先聲明調用unti1,也就是要加上
uses unit1;
這樣就能夠在該線程單元直接調用主form的控件了,好比在unit3中能夠寫:
form1.edit2.text:=inttostr(i)
瞭解了這些基本規則,就能夠寫出比較複雜的多線程程序了。
還有一點要說明的,默認生成的線程單元,調用的單元只有一個:
uses classes;
這樣,每每不少函數和對象在線程單元裏不能使用,因此在必要時,應該根據須要user
相應的單元,這個例程爲了簡單,把大部分經常使用的單元都拷過去了,這並非推薦的辦法,因
爲這樣一來會使程序的垃圾過多,因此,通常要用什麼拷什麼。
3、經常使用的api 函數
在處理多線程問題的時候,也常常用到windows提供的api 函數,須要說明的是,tthread
對象內部封裝的方法,其實主要也是調用api 函數,可是,考慮更全面,更安全。而直接調用
api 函數,每每會由於運用不當,出現一些不該有的錯誤。因此,我我的覺得,只要用tthread
對象的方法能解決的,就不要直接調用api 函數,api 函數只應該在用在tthread 對象方法
解決不了的時候。
例如tthread 對象方法內部調用api 函數的時候,通常使用推薦的默認值,但須要更精細
的控制時,就能夠直接使用api 函數。
其實,tthread 對象方法已經受到了大多數程序設計者的承認,好比,原來vb是不具有直
接處理多線程的能力的,可是,如今vb.net就宣稱,它具有了簡單處理多線程問題的能力,這就
很說明問題。
下面簡單介紹幾種api 函數,爲了清晰方便,這裏着重在於說明,函數正確的描述能夠本身
閱讀書上的例子和手冊:
構建線程:
createthread(參數1,--安全屬性(通常=nil,默認安全屬性)
參數2,--線程堆棧尺(通常=0,與主線程相同長度,並且能夠根據須要自動變化)
參數3,--指向函數名指針,@函數名,這個參數十分重要,不正確將沒法調用成功。
參數4,--用戶須要向線程傳遞的參數,是一個指向結構的指針,不需傳遞參數時,爲nil。
參數5)--傳入與線程有關的一些參數,例如:
create_suspended 建立一個掛起的線程;
0 建立後當即激活。
書上有這個函數應用的十分清晰的例子,能夠本身閱讀。
通常並不推薦使用 createtheard函數,而推薦使用rtl 庫裏的system單元中定義的
begintheard函數,由於這除了能建立一個線程和一個入口函數之外,還增長了幾項保護措施,
具體的請參閱書上的第10頁說明。
對應suspend(掛起)和resume(喚醒)的兩個api 函數爲:
function suspendthread(hthread:thandle):dword;
function resumethread(hthread:thandle):dword;
其中,thandle被要求控制線程的句柄,函數調用成功,返回掛起的次數,調用不成功。
則返回0xffffffff。
4、線程的終止和退出:
1)自動退出:
一個線程從execute()過程當中退出,即意味着線程的終止,此時將調用windows的
exitthread()函數來清除線程所佔用的堆棧。若是線程對象的 freeonterminate 屬性設爲
true,則線程對象將自動刪除,並釋放線程所佔用的資源。
這是消除線程對象最簡單的辦法。
2)受控退出:
利用線程對象的terminate屬性,能夠由進程或者由其餘線程控制線程的退出。只須要
簡單的調用該線程的terminate方法,並設直線程對象的terminate屬性爲true。在線程中,應
該不斷監視terminate的值,一旦發現爲true,則退出,例如在execute()過程當中能夠這樣寫:
while not terminate do
begin
........
end;
3)退出的api 函數:
關於線程退出的api 函數聲明以下:code
function terminatethread(hthread:thandle;dwexitcode:dword);
不過,這個函數會使代碼馬上終止,而無論程序中有沒有
try....finally
機制,可能會致使錯誤,不到萬不得已,最好不要使用。
4) 利用掛起線程的方法(suspend)
利用掛起線程的suspend方法,後面跟個free,也能夠釋放線程,例如:
thread1.suspend; //掛起
thread2.free; //釋放
書上有相應的例子。
5、 線程的優先級:
在多線程的狀況下,通常要根據線程執行任務的重要性,給線程適當的優先級,通常如
果量的線程同時申請cpu 時間,優先級高的線程優先。
在windows下,給線程的優先級分爲30級,而delphi中tthread 對象相對簡單的把優先
級分爲七級。也就是在tthread中聲明瞭一個枚舉類型ttthreadpriority:
type
ttthreadpriority(tpidle,tplowest,tplower,tpnormal,tphight,tphighest,tptimecrital)
分別對應的是最低(系統空閒時有效,-15),較低(-2),低(-1),正常(普通0)
,高(1),較高(2),最高(15)。
其中tpidle和tptimecrital有些特殊,具體狀況請閱讀書上有關內容。
設置優先級可以使用thread對象的priority屬性: threadobject.priority:=tthreadpriority(級別);