| 導購 | 订阅 | 在线投稿
分享
 
 
 

WIN32下DELPHI中的多線程【線程的調度】(二)

來源:互聯網網民  2006-12-12 19:18:06  評論

線程的調度

每個線程是擁有一個上下文結構的,這個結構維護在線程的內核對象中。這個上下文結構反映了線程上次運行時該線程的C P U寄存器的狀態。每隔20ms左右,Windows要查看當前存在的所有線程內核對象。在這些對象中,只有某些對象被視爲可以調度的對象。Windows選擇可調度的線程內核對象中的一個,將它加載到C P U的寄存器中,它的值是上次保存在線程的環境中的值。這項操作稱爲上下文轉換。Windows實際上保存了一個記錄,它說明每個線程獲得了多少個運行機會。

Windows被稱爲搶占式多線程操作系統,因爲一個線程可以隨時停止運行,隨後另一個線程可進行調度。如你所見,可以對它進行一定程度的控制,但是不能太多。注意,無法保證線程總是能夠運行,也不能保證線程能夠得到整個進程,無法保證其他線程不被允許運行等等。

我在編寫串口通訊程序的時候,起初,我有一個天真的想法,“在win32平台下,如何能夠保證從串口傳送過來的數據,在數據到達後1MS內開始運行?”。爲此,我曾經做了許多試驗,但當我真正了解了一些win32平台的知識,我得到了答案,辦不到。只有實時操作系統才能作出這樣的承諾,但Windows不是實時操作系統。實時操作系統必須清楚地知道它是在什麽硬件上運行,這樣它才能知道它的硬盤控制器和鍵盤等的等待時間。Microsoft對Windows規定的目標是,使它能夠在各種不同的硬件上運行,即能夠在不同的CPU、不同的驅動器和不同的網絡上運行。簡而言之,Windows沒有設計成爲一種實時操作系統。

Windows系統只調度可以調度的線程。那麽什麽是可以調度的線程,什麽是不可以調度的線程呢?例如,有些線程對象的暫停計數大于1(記錄在線程內核對象的上下文結構中)。這意味著該線程已經暫停運行,不應該給它安排任何C P U時間。還記得上文中曾經提到的CREATE_SUSPENDED標志嗎?在創建一個線程的時候,createThread函數接收的倒數第二個參數中賦值CREATE_SUSPENDED就可以創建一個暫停的線程。除了暫停的線程外,其他許多線程也是不可調度的線程,因爲它們正在等待某些事情的發生。例如,如果記事本程序,如果你不鍵入任何數據,那麽它的線程就沒有什麽事情要做。系統不給無事可做的線程分配CPU時間。當移動它的窗口時,或者它的窗口需要刷新它的內容,或者將數據鍵入記事本,系統就會自動使它的線程成爲可調度的線程。但切記,這並不意味著它的線程立即獲得了CPU時間。它只是表示記事本的的線程有事情可做,系統將設法在某個時間(不久的將來)對它進行調度。

線程的暫停和執行

我們前面說過,在線程內核對象的內部有一個值,用于指明線程的暫停計數。當調用CreateThread函數時,就創建了線程的內核對象,並且它的暫停計數被初始化爲1。這可以防止線程被調度到CPU中。當然,這是很有用的,因爲線程的初始化需要時間,你不希望在系統做好充分的准備之前就開始執行線程。當線程完全初始化好了之後, 要查看是否已經傳遞了CREATE_SUSPENDED標志。如果已經傳遞了這個標志,那麽這些函數就返回,同時新線程處于暫停狀態。如果尚未傳遞該標志,那麽該函數將線程的暫停計數遞減爲0。當線程的暫停計數是0的時候,除非線程正在等待其他某種事情的發生,否則該線程就處于可調度狀態。

在暫停狀態中創建一個線程,就能夠在線程有機會執行任何代碼之前改變線程的運行環境(如優先級)。一旦改變了線程的環境,必須使線程成爲可調度線程。要進行這項操作,可以調用ResumeThread,將線程句柄傳遞給它,如果ResumeThread,函數運行成功,它將返回線程的前一個暫停計數,否則返回0xFFFFFFFF。注意這裏,它返回的是前一個暫停計數。

單個線程可以暫停若幹次。如果一個線程暫停了3次,它必須恢複3次,然後它才可以被分配給一個C P U。當創建線程時,除了使用CREATE_SUSPENDED外,也可以調用SuspendThread函數來暫停線程的運行。任何線程都可以調用該函數來暫停另一個線程的運行(只要擁有線程的句柄)。不用說,線程可以自行暫停運行,但是不能自行恢複運行。SuspendThread返回的是線程的前一個暫停計數。線程暫停的最多次數可以是MAXIMUM_SUSPEND_COUNT次。值得注意的是,SuspendThread與內核方式的執行是異步進行的,但是在線程恢複運行之前,不會發生用戶方式的執行。在實際環境中,調用SuspendThread時必須小心,因爲不知道暫停線程運行時它在進行什麽操作。如果線程試圖從堆棧中分配內存,那麽該線程將在該堆棧上設置一個鎖。當其他線程試圖訪問該堆棧時,這些線程的訪問就被停止,直到第一個線程恢複運行。只有確切知道目標線程是什麽(或者目標線程正在做什麽),並且采取強有力的措施來避免因暫停線程的運行而帶來的問題或死鎖狀態,SuspendThread才是安全的。

線程的睡眠

線程也能告訴系統,它不想在某個時間段內被調度。這是通過調用Sleep函數來實現的:

VOID Sleep(DWORD cMilliseconds)

該函數可使線程暫停自己的運行,直到cMilliseconds過去爲止。關于Sleep函數,有下面幾個重要問題值得注意:

• 調用Sleep,可使線程自願放棄它剩余的時間片。

• 系統將在大約的指定毫秒數內使線程不可調度。不錯,如果告訴系統,想睡眠100ms,那麽可以睡眠大約這麽長時間,但是也可能睡眠數秒鍾或者數分鍾。還是那個反複重申的概念, Windows不是個實時操作系統。雖然線程可能在規定的時間被喚醒,但是它能否做到,取決于系統

中還有什麽操作正在進行。

• 可以調用Sleep,並且爲cMilliseconds)參數傳遞INFINITE。這將告訴系統永遠不要調度該線程。這不是一件值得去做的事情。最好是讓線程退出,並還原它的堆棧和內核對象。

• 可以將0傳遞給Sleep。這將告訴系統,調用線程將釋放剩余的時間片,並迫使系統調度另一個線程。但是,系統可以對剛剛調用Sleep的線程重新調度。如果不存在多個擁有相同優先級的可調度線程,就會出現這種情況。Sleep(0)是一個非常有意思的方法。要小心Sleep()神秘的時間調整問題。Sleep()可能會使你的機器出現特別的問題。這種問題在另一台機器上可能無法再現。

切換到另一個線程

系統提供了一個稱爲SwitchToThread的函數,使得另一個可調度線程(如果存在能夠運行)。當調用這個函數的時候,系統要查看是否存在一個迫切需要C P U時間的線程。如果沒有線程迫切需要C P U時間SwitchToThread就會立即返回。如果存在一個迫切需要C P U時間的線程,SwitchToThread就對該線程進行調度(該線程的優先級可能低于調用SwitchToThread的線程)。這個迫切需要C P U時間的線程可以運行一個時間段,然後系統調度程序照常運行。該函數允許一個需要資源的線程強制另一個優先級較低、而目前卻擁有該資源的線程放棄該資源。如果調用SwitchToThread函數時沒有其他線程能夠運行,那麽該函數返回FALSE,否則返回一個非0值。調用SwitchToThread函數與調用Sleep是相似的,差別是SwitchToThread允許優先級較低的線程運行。即使低優先級線程迫切需要CPU時間,而Sleep則可能因爲優先級關系使得剛放棄CPU的線程被立即重新調度。

優先級

操作系統會負責爲每個線程分配CPU時間。一個線程所分配到的CPU時間主要取決于該線程的優先級,而線程的優先級又取決于進程的優先級類和線程本身的相對優先級。

1. 進程的優先級類

進程的優先級類用來描述一個進程的優先程度。Win32支持四種不同的優先級類: Idle、Normal、High 和Realtime。其中,Normal是默認的優先級。在Windows單元中,每一種優先級類都對應著一個標志。當要進行進程的優先級設置時,可以用一種優先級類與CreateProcess()的參數dwCreationFlags進行或操作。另外,還可以動態地爲一個已有的進程調整優先級類。這時候,通常你要用到下面API函數

bool SetPriorityClass(HANDLE hProcess,DWORD fdwPriority),其中第一個參數是進程的句柄,你可以通過GetCurrentProcess來獲得當前進程的句柄。每個優先級類也對應一個數字,值在4~ 24之間。注意在Windows NT/2000下,要有特殊的權限才能修改進程的優先類。默認的設置允許進程設置它們的優先級類,但是,這些都可以由系統管理員來關閉,尤其是在高負載的WinNT/2000服務器上。

大多數情況下,進程的優先級類不要被設爲Realtime。因爲,大多數操作系統本身的線程的優先級類比Realtime低。如果一個進程得到的C P U時間比操作系統本身還多,後果是無法想象的。即使將進程的優先級類設爲High ,也可能引起問題。因爲,當高優先級的線程沒有大部分空時間或等待外部事件時,它要從低優先級的線程和進程中搶奪CPU時間,直到它被一事件阻塞或處于空閑狀態或處理消息。所以,在搶占式多任務操作系統中如果不能合理地安排優先級,就很容易崩潰。

優先級類

說明

實時

進程中的線程必須立即對事件作出響應,以便執行關鍵時間的任務。

該進程中的線程還會搶先于操作系統組件之前運行。使用本優先級類

時必須極端小心

進程中的線程必須立即對事件作出響應,以便執行關鍵時間的任務。

Task Manager(任務管理器)在這個類上運行,以便用戶可以撤消脫

離控制的進程

高于正常

進程中的線程在正常優先級與高優先級之間運行(這是Wi n d o w s

2 0 0 0中的新優先級類)

正常

進程中的線程沒有特殊的調度需求

低于正常

進程中的線程在正常優先級與空閑優先級之間運行(這是Wi n d o w s

2 0 0 0中的新優先級類)

空閑

進程中的線程在系統空閑時運行。該進程通常由屏幕保護程序或後

台實用程序和搜集統計數據的軟件使用

2. 相對優先級

決定一個線程全面的優先級的另一方面是相對優先級。優先級類是針對進程的,它對進程內部的

所有線程都有效。而相對優先級是針對某個線程的。一個線程的相對優先級可設爲以下七種: Idle、Lowest、Below Normal、Normal、Above Normal、Highest 和Time Critical。

要設置一個線程的相對優先級,可以通過API函數SetThreadPriority來完成,再DELPHI中,你可以通過TThread對象的Priority屬性來設置。獲得線程相對優先級的API函數是int GetThreadPriority(HANDLE hThread);

系統何如根據優先級來調度線程

每個線程都會被賦予一個從0(最低)到31(最高)的優先級號碼。當系統確定將哪個線程分配給CPU時,它首先觀察優先級爲31的線程,並以循環方式對它們進行調度。如果優先級爲31的線程可以調度,那麽就將該線程賦予一個CPU。在該線程的時間片結束時,系統要查看是否還有另一個優先級爲31的線程可以運行,如果有,它將允許該線程被賦予一個CPU。只要優先級爲31的線程是可調度的,系統就絕對不會將優先級爲0到30的線程分配給C P U。這種情況稱爲渴求調度(starvation)。當高優先級線程使用大量的CPU時間,從而使得低優先級線程無法運行時,便會出現渴求情況。在多處理器計算機上出現渴求情況的可能性要少得多,因爲在這樣的計算機上,優先級爲31和優先級爲30的線程能夠同時運行。系統總是設法使CPU保持繁忙狀態,只有當沒有線程可以調度的時候, CPU才處于空閑狀態。

人們可能認爲,在這樣的系統中,低優先級線程永遠得不到機會運行。不過正像前面指出的那樣,在任何一個時段內,系統中的大多數線程是不能調度的。例如,如果進程的主線程調用GetMessage函數,而系統發現沒有線程可以供它使用,那麽系統就暫停進程的線程運行,釋放該線程的剩余時間片,並且立即將CPU分配給另一個等待運行的線程。如果沒有爲GetMessage函數顯示可供檢索的消息,那麽進程的線程將保持暫停狀態,並且決不會被分配給CPU。但是,當消息被置于線程的隊列中時,系統就知道該線程不應該再處于暫停狀態。此時,如果沒有更高優先級的線程需要運行,系統就將該線程分配給一個CPU。

高優先級線程將搶在低優先級線程之前運行,不管低優先級線程正在運行什麽。例如,如果一個優先級爲5的線程正在運行,系統發現一個高優先級的線程准備要運行,那麽系統就會立即暫停低優先級線程的運行(即使它處于它的時間片中),並且將C P U分配給高優先級線程,使它獲得一個完整的時間片。還有,當系統引導時,它會創建一個特殊的線程,稱爲0頁線程。該線程被賦予優先級0,它是整個系統中唯一的一個在優先級0上運行的線程。當系統中沒有任何線程需要執行操作時,0頁線程負責將系統中的所有空閑R A M頁面置0。

動態提高線程的優先級等級

通過將線程的相對優先級與線程的進程優先級類綜合起來考慮,系統就可以確定線程的優先級等級。有時這稱爲線程的基本優先級等級。

系統常常要提高線程的優先級等級,以便對窗口消息或讀取磁盤等I/O事件作出響應。

例如,在高優先級類進程中的一個正常優先級等級的線程的基本優先級等級是13。如果用戶按下一個操作鍵,系統就會將一個WM_KEYDOWN消息放入線程的隊列中。由于一個消息已經出現在線程的隊列中,因此該線程就是可調度的線程。此外,鍵盤設備驅動程序也能夠告訴系統暫時提高線程的優先級等級。該線程的優先級等級可能提高2級,其當前優先級等級改爲15。系統在優先級爲15時爲一個時間片對該線程進行調度。一旦該時間片結束,系統便將線程的優先級遞減1,使下一個時間片的線程優先級降爲14。該線程的第三個時間片按優先級等級13來執行。如果線程要求執行更多的時間片,均按它的基本優先級等級13來執行。注意,線程的當前優先級等級決不會低于線程的基本優先級等級。此外,導致線程成爲可調度線程的設備驅動程序可以決定優先級等級提高的數量。Microsoft並沒有規定各個設備驅動程序可以給線程的優先級提高多少個等級。這樣就使得Microsoft可以不斷地調整線程優先級提高的動態等級,以確定最佳的總體響應性能。系統只能爲基本優先級等級在1至15之間的線程提高其優先級等級。實際上這是因爲這個範圍稱爲動態優先級範圍。此外,系統決不會將線程的優先級等級提高到實時範圍(高于15)。由于實時範圍中的線程能夠執行大多數操作系統的函數,因此給等級的提高規定一個範圍,就可以防止應用程序幹擾操作系統的運行。另外,系統決不會動態提高實時範圍內的線程優先級等級。

另一種情況也會導致系統動態地提高線程的優先級等級。比如有一個優先級爲4的線程准備運行但是卻不能運行,因爲一個優先級爲8的線程正連續被調度。在這種情況下,優先級爲4的線程就非常渴望得到CPU時間。當系統發現一個線程在大約3至4s內一直渴望得到C P U時間,它就將這個渴望得到CPU時間的線程的優先級動態提高到15,並讓該線程運行兩倍于它的時間量。當到了兩倍時間量的時候,該線程的優先級立即返回到它的基本優先級。

系統動態的改變優先級,在我們編程的時候會産生不良影響,爲此,還有兩個API函數可以使得系統的此功能不起作用。

bool SetProcessPriorityBoost(HANDLE hProcess,bool disablePriorityboost);

bool SetThreadPriorityBoost(HANDLE hThread,bool disablePriorityboost);

從名字你就應該可以看出,第一個API函數可以激活或停用指定進程所有線程的優先級提高功能,而後面一個則是針對特定線程的。

例子:關鍵的代碼如下

WIN32下DELPHI中的多線程【線程的調度】(二)
WIN32下DELPHI中的多線程【線程的調度】(二)
...{
WIN32下DELPHI中的多線程【線程的調度】(二)
作者:wudi_1982
WIN32下DELPHI中的多線程【線程的調度】(二)
聯系方式:wudi_1982@hotmail.com
WIN32下DELPHI中的多線程【線程的調度】(二)
轉載請著名出處
WIN32下DELPHI中的多線程【線程的調度】(二)
本代碼旨在演示線程的調度,很多位置沒有加入適當的控制和資源釋放,請按照後續操作執行
WIN32下DELPHI中的多線程【線程的調度】(二)
}
WIN32下DELPHI中的多線程【線程的調度】(二)
type
WIN32下DELPHI中的多線程【線程的調度】(二)
TSleepType=(stSleep,stSwitch);
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
//演示線程調度的TThread派生類
WIN32下DELPHI中的多線程【線程的調度】(二)
TPriThread1=class(TThread)
WIN32下DELPHI中的多線程【線程的調度】(二)
private
WIN32下DELPHI中的多線程【線程的調度】(二)
CurCount : integer; //當前計數
WIN32下DELPHI中的多線程【線程的調度】(二)
Flb : TLabel; //用來顯示當前計數的label
WIN32下DELPHI中的多線程【線程的調度】(二)
FCanSleep : Boolean; //是否自動釋放時間片
WIN32下DELPHI中的多線程【線程的調度】(二)
FSleepMs : integer;
WIN32下DELPHI中的多線程【線程的調度】(二)
FSleepType : TSleepType;//釋放時間片的方式
WIN32下DELPHI中的多線程【線程的調度】(二)
procedure GetRestult;
WIN32下DELPHI中的多線程【線程的調度】(二)
protected
WIN32下DELPHI中的多線程【線程的調度】(二)
procedure Execute;override;
WIN32下DELPHI中的多線程【線程的調度】(二)
public
WIN32下DELPHI中的多線程【線程的調度】(二)
constructor Create(CreateSuspended: Boolean;ALabel : TLabel);
WIN32下DELPHI中的多線程【線程的調度】(二)
property CanSleep : boolean read FCanSleep write FCanSleep;
WIN32下DELPHI中的多線程【線程的調度】(二)
property SleepMs : integer read FSleepMs write FSleepMs;
WIN32下DELPHI中的多線程【線程的調度】(二)
property SleepType : TSleepType read FSleepType write FSleepType;
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
....
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
WIN32下DELPHI中的多線程【線程的調度】(二)
...{ TPriThread1的實現 }
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
constructor TPriThread1.Create(CreateSuspended: Boolean; ALabel: TLabel);
WIN32下DELPHI中的多線程【線程的調度】(二)
begin
WIN32下DELPHI中的多線程【線程的調度】(二)
//構造函數
WIN32下DELPHI中的多線程【線程的調度】(二)
flb := ALabel;
WIN32下DELPHI中的多線程【線程的調度】(二)
FSleepMs := 0;
WIN32下DELPHI中的多線程【線程的調度】(二)
FCanSleep := true;
WIN32下DELPHI中的多線程【線程的調度】(二)
FSleepType := stSleep;
WIN32下DELPHI中的多線程【線程的調度】(二)
inherited create(CreateSuspended);
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
procedure TPriThread1.Execute;
WIN32下DELPHI中的多線程【線程的調度】(二)
var
WIN32下DELPHI中的多線程【線程的調度】(二)
i : integer;
WIN32下DELPHI中的多線程【線程的調度】(二)
begin
WIN32下DELPHI中的多線程【線程的調度】(二)
inherited;
WIN32下DELPHI中的多線程【線程的調度】(二)
FreeOnTerminate := true;
WIN32下DELPHI中的多線程【線程的調度】(二)
CurCount := 0;
WIN32下DELPHI中的多線程【線程的調度】(二)
for i := 0 to 100000 do
WIN32下DELPHI中的多線程【線程的調度】(二)
begin
WIN32下DELPHI中的多線程【線程的調度】(二)
CurCount := i;//改變當前計數
WIN32下DELPHI中的多線程【線程的調度】(二)
Synchronize(GetRestult);//顯示結果
WIN32下DELPHI中的多線程【線程的調度】(二)
if FCanSleep then//是否自動釋放時間片
WIN32下DELPHI中的多線程【線程的調度】(二)
begin
WIN32下DELPHI中的多線程【線程的調度】(二)
//根據釋放時間片的不同方式進行相應操作
WIN32下DELPHI中的多線程【線程的調度】(二)
case FSleepType of
WIN32下DELPHI中的多線程【線程的調度】(二)
stSleep : Sleep(SleepMs);//睡眠
WIN32下DELPHI中的多線程【線程的調度】(二)
stSwitch : SwitchToThread;//調用其他線程
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
procedure TPriThread1.GetRestult;
WIN32下DELPHI中的多線程【線程的調度】(二)
begin
WIN32下DELPHI中的多線程【線程的調度】(二)
flb.Caption := IntToStr(CurCount);
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
WIN32下DELPHI中的多線程【線程的調度】(二)
...{Form1的主要代碼}
WIN32下DELPHI中的多線程【線程的調度】(二)
procedure TForm1.btnPThread1CreateClick(Sender: TObject);
WIN32下DELPHI中的多線程【線程的調度】(二)
begin
WIN32下DELPHI中的多線程【線程的調度】(二)
//生成兩個線程
WIN32下DELPHI中的多線程【線程的調度】(二)
MyPThread1 := TPriThread1.Create( not ckbx1State.Checked,lab1);
WIN32下DELPHI中的多線程【線程的調度】(二)
MyPThread2 := TPriThread1.Create(not ckbx2State.Checked,lab2);
WIN32下DELPHI中的多線程【線程的調度】(二)
//得到他們當前的優先級
WIN32下DELPHI中的多線程【線程的調度】(二)
lb1p.Caption := inttostr(GetThreadPriority(MyPThread1.Handle));
WIN32下DELPHI中的多線程【線程的調度】(二)
lb2p.Caption := inttostr(GetThreadPriority(MyPThread2.Handle));
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
procedure TForm1.btnPThread1ResClick(Sender: TObject);
WIN32下DELPHI中的多線程【線程的調度】(二)
begin
WIN32下DELPHI中的多線程【線程的調度】(二)
//執行線程
WIN32下DELPHI中的多線程【線程的調度】(二)
MyPThread1.Resume;
WIN32下DELPHI中的多線程【線程的調度】(二)
ckbx1State.Checked := true;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
MyPThread2.Resume;
WIN32下DELPHI中的多線程【線程的調度】(二)
ckbx2State.Checked := true;
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
procedure TForm1.btnPThread1SudClick(Sender: TObject);
WIN32下DELPHI中的多線程【線程的調度】(二)
begin
WIN32下DELPHI中的多線程【線程的調度】(二)
//挂起線程
WIN32下DELPHI中的多線程【線程的調度】(二)
MyPThread1.Suspend;
WIN32下DELPHI中的多線程【線程的調度】(二)
ckbx1State.Checked := false;
WIN32下DELPHI中的多線程【線程的調度】(二)
MyPThread2.Suspend;
WIN32下DELPHI中的多線程【線程的調度】(二)
ckbx2State.Checked := false;
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
procedure TForm1.btnUpPThread1Click(Sender: TObject);
WIN32下DELPHI中的多線程【線程的調度】(二)
begin
WIN32下DELPHI中的多線程【線程的調度】(二)
//在線程挂起時,提高第一個線程的相對優先級
WIN32下DELPHI中的多線程【線程的調度】(二)
MyPThread1.Priority := tpHigher;
WIN32下DELPHI中的多線程【線程的調度】(二)
//顯示當前的優先級到屏幕
WIN32下DELPHI中的多線程【線程的調度】(二)
lb1p.Caption := inttostr(GetThreadPriority(MyPThread1.Handle));
WIN32下DELPHI中的多線程【線程的調度】(二)
// MyPThread2.Priority := tpHigher;
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
procedure TForm1.btnUpdateSleepClick(Sender: TObject);
WIN32下DELPHI中的多線程【線程的調度】(二)
begin
WIN32下DELPHI中的多線程【線程的調度】(二)
//修改兩個線程的時間片釋放方式
WIN32下DELPHI中的多線程【線程的調度】(二)
MyPThread1.CanSleep := ckbxAllowSleep1.Checked;
WIN32下DELPHI中的多線程【線程的調度】(二)
case RadioGroup1.ItemIndex of
WIN32下DELPHI中的多線程【線程的調度】(二)
0 : MyPThread1.SleepType := stSleep;
WIN32下DELPHI中的多線程【線程的調度】(二)
1 : MyPThread1.SleepType := stSwitch;
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
MyPThread2.CanSleep := ckbxAllowSleep2.Checked;
WIN32下DELPHI中的多線程【線程的調度】(二)
case RadioGroup2.ItemIndex of
WIN32下DELPHI中的多線程【線程的調度】(二)
0 : MyPThread2.SleepType := stSleep;
WIN32下DELPHI中的多線程【線程的調度】(二)
1 : MyPThread2.SleepType := stSwitch;
WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)
end;
WIN32下DELPHI中的多線程【線程的調度】(二)

WIN32下DELPHI中的多線程【線程的調度】(二)

窗體效果:

WIN32下DELPHI中的多線程【線程的調度】(二)

讓我們來用這個程序測試一些效果:

1、基本執行。程序運行之後,使用默認設置,點擊【創建線程】按鈕,線程將被創建,並且挂起,這是你可以間隔的點擊【執行線程】和【挂起線程】按鈕,你會在屏幕上看到線程的當前計數,注意這兩個計數之間的差值,以及整個界面的執行效果(我指的是在你讓線程不斷的執行和挂起之間界面是否會出現不刷新的情況),當線程執行完畢之後,關閉程序。

2、通過Sleep(0)釋放時間片演示線程調度。運行程序,使用默認設置,點擊【創建線程】按鈕,然後將兩個線程的自釋放時間片功能統統去掉(也就是去掉ckbxAllowSleep1 and 2的勾勾),然後點擊【修改睡眠方式】按鈕,隨後你可以進行間隔點擊【執行線程】和【挂起線程】按鈕,多做幾次這樣的操作,觀察兩個計數之間的差值,和測試1的差值比較一下。我想你應該能想到些什麽。然後,幾乎可以肯定你的界面將會出現無法刷新的情況,並且你的鼠標無法立即在此界面上進行其他的操作。這個時候,稍等一下,你會發現過了一會兒,兩個當前計數都被刷新了。爲什麽??這時,我們除了考慮我們創建的兩個線程之外,你還要考慮的你程序本身的主線程以及其他可能存在的附屬線程,我們再去程序中線程的那段循環代碼,

CurCount := i;//改變當前計數

Synchronize(GetRestult);//顯示結果

if FCanSleep then//是否自動釋放時間片

begin

//根據釋放時間片的不同方式進行相應操作

case FSleepType of

stSleep : Sleep(SleepMs);//睡眠

stSwitch : SwitchToThread;//調用其他線程

end;

end;

你應該看到線程將當前計數顯示在屏幕上的操作是執行了Synchronize(GetRestult),這裏,因爲我們的線程和VCL界面發生了交互,我們必須對Synchronize有所了解,去看VCL的源碼,你會發現,當你在程序中第一次創建一個附屬線程時, VCL將會從主線程環境中創建和維護一個隱含的線程窗口。此窗口唯一的目的是把通過Synchronize()調用的方法排隊。Synchronize()把由Method參數傳遞過來的方法保存在TThread的FMethod字段中,然後,給線程窗口發一個CM_EXECPROC消息,並且把消息的lParam參數設爲self(這裏指線程對象)。當線程窗口的窗口過程收到這個消息後,它就調用FMethod字段所指定的方法。由于線程窗口是在主線程內創建的,線程窗口的窗口過程也將被主線程執行。因此,FMethod字段所指定的方法就在主線程內執行。

在我們選擇釋放時間片的模式下,在這裏,無論我們是用Sleep還是SwitchToThread,當前線程都會立即釋放時間片,因爲這時我們並沒有修改線程的優先級,他們都在同樣的優先級環境下運行,那麽當占用CPU的線程釋放時間片後,其他線程將可以相對輕松的得到CPU,所以在使用釋放時間片的模式下,界面的刷新會良好。並且調度相對有序。

3、Sleep和SwitchToThread區別的演示。運行程序,使用默認設置,點擊【創建線程】按鈕,然後點擊【提高線程1的優先級】按鈕,再點擊【執行線程】這是,兩個線程將不再是同樣的優先級,其他設置依然是默認的(使用sleep方式釋放時間片),你會看到線程1首先執行,線程2處于可調度模式,但並沒有被調度(當前計數沒有刷新),並且屏幕也不刷新,在稍等一段時間之後,屏幕刷新,線程2也開始運行,並且此時屏幕刷新正常。爲什麽呢?回頭去看本文上面的內容,當線程1的優先級提高之後,系統會首先調度它,雖然它使用sleep(0)來釋放時間片,但當時間片釋放後,因爲它的優先級相對較高,系統依然會調度線程1,所以此時,線程2將不能執行,界面也不能有效刷新。在這個思路下,再做一個測試,使用同樣的方式,只不過這次,在線程執行之前,除了提高線程1的優先級之外,還將線程1釋放時間片方式改爲SwitchToThread,此時你就可以看到兩個線程都有機會執行,並且界面也將有效刷新。

4、你還可以做其他配置信息的測試,相信會加深對WIN32平台下線程調度的了解。

參考文獻

1、《DELPHI5開發人員指南》

2、《WINDOWS核心編程》

注:以上程序在D7+WINXP下測試通過

轉載請著名出處。

 
特别声明:以上内容(如有图片或视频亦包括在内)为网络用户发布,本站仅提供信息存储服务。
 
線程的調度 每個線程是擁有一個上下文結構的,這個結構維護在線程的內核對象中。這個上下文結構反映了線程上次運行時該線程的C P U寄存器的狀態。每隔20ms左右,Windows要查看當前存在的所有線程內核對象。在這些對象中,只有某些對象被視爲可以調度的對象。Windows選擇可調度的線程內核對象中的一個,將它加載到C P U的寄存器中,它的值是上次保存在線程的環境中的值。這項操作稱爲上下文轉換。Windows實際上保存了一個記錄,它說明每個線程獲得了多少個運行機會。 Windows被稱爲搶占式多線程操作系統,因爲一個線程可以隨時停止運行,隨後另一個線程可進行調度。如你所見,可以對它進行一定程度的控制,但是不能太多。注意,無法保證線程總是能夠運行,也不能保證線程能夠得到整個進程,無法保證其他線程不被允許運行等等。 我在編寫串口通訊程序的時候,起初,我有一個天真的想法,“在win32平台下,如何能夠保證從串口傳送過來的數據,在數據到達後1MS內開始運行?”。爲此,我曾經做了許多試驗,但當我真正了解了一些win32平台的知識,我得到了答案,辦不到。只有實時操作系統才能作出這樣的承諾,但Windows不是實時操作系統。實時操作系統必須清楚地知道它是在什麽硬件上運行,這樣它才能知道它的硬盤控制器和鍵盤等的等待時間。Microsoft對Windows規定的目標是,使它能夠在各種不同的硬件上運行,即能夠在不同的CPU、不同的驅動器和不同的網絡上運行。簡而言之,Windows沒有設計成爲一種實時操作系統。 Windows系統只調度可以調度的線程。那麽什麽是可以調度的線程,什麽是不可以調度的線程呢?例如,有些線程對象的暫停計數大于1(記錄在線程內核對象的上下文結構中)。這意味著該線程已經暫停運行,不應該給它安排任何C P U時間。還記得上文中曾經提到的CREATE_SUSPENDED標志嗎?在創建一個線程的時候,createThread函數接收的倒數第二個參數中賦值CREATE_SUSPENDED就可以創建一個暫停的線程。除了暫停的線程外,其他許多線程也是不可調度的線程,因爲它們正在等待某些事情的發生。例如,如果記事本程序,如果你不鍵入任何數據,那麽它的線程就沒有什麽事情要做。系統不給無事可做的線程分配CPU時間。當移動它的窗口時,或者它的窗口需要刷新它的內容,或者將數據鍵入記事本,系統就會自動使它的線程成爲可調度的線程。但切記,這並不意味著它的線程立即獲得了CPU時間。它只是表示記事本的的線程有事情可做,系統將設法在某個時間(不久的將來)對它進行調度。 線程的暫停和執行 我們前面說過,在線程內核對象的內部有一個值,用于指明線程的暫停計數。當調用CreateThread函數時,就創建了線程的內核對象,並且它的暫停計數被初始化爲1。這可以防止線程被調度到CPU中。當然,這是很有用的,因爲線程的初始化需要時間,你不希望在系統做好充分的准備之前就開始執行線程。當線程完全初始化好了之後, 要查看是否已經傳遞了CREATE_SUSPENDED標志。如果已經傳遞了這個標志,那麽這些函數就返回,同時新線程處于暫停狀態。如果尚未傳遞該標志,那麽該函數將線程的暫停計數遞減爲0。當線程的暫停計數是0的時候,除非線程正在等待其他某種事情的發生,否則該線程就處于可調度狀態。 在暫停狀態中創建一個線程,就能夠在線程有機會執行任何代碼之前改變線程的運行環境(如優先級)。一旦改變了線程的環境,必須使線程成爲可調度線程。要進行這項操作,可以調用ResumeThread,將線程句柄傳遞給它,如果ResumeThread,函數運行成功,它將返回線程的前一個暫停計數,否則返回0xFFFFFFFF。注意這裏,它返回的是前一個暫停計數。 單個線程可以暫停若幹次。如果一個線程暫停了3次,它必須恢複3次,然後它才可以被分配給一個C P U。當創建線程時,除了使用CREATE_SUSPENDED外,也可以調用SuspendThread函數來暫停線程的運行。任何線程都可以調用該函數來暫停另一個線程的運行(只要擁有線程的句柄)。不用說,線程可以自行暫停運行,但是不能自行恢複運行。SuspendThread返回的是線程的前一個暫停計數。線程暫停的最多次數可以是MAXIMUM_SUSPEND_COUNT次。值得注意的是,SuspendThread與內核方式的執行是異步進行的,但是在線程恢複運行之前,不會發生用戶方式的執行。在實際環境中,調用SuspendThread時必須小心,因爲不知道暫停線程運行時它在進行什麽操作。如果線程試圖從堆棧中分配內存,那麽該線程將在該堆棧上設置一個鎖。當其他線程試圖訪問該堆棧時,這些線程的訪問就被停止,直到第一個線程恢複運行。只有確切知道目標線程是什麽(或者目標線程正在做什麽),並且采取強有力的措施來避免因暫停線程的運行而帶來的問題或死鎖狀態,SuspendThread才是安全的。 線程的睡眠 線程也能告訴系統,它不想在某個時間段內被調度。這是通過調用Sleep函數來實現的: VOID Sleep(DWORD cMilliseconds) 該函數可使線程暫停自己的運行,直到cMilliseconds過去爲止。關于Sleep函數,有下面幾個重要問題值得注意: • 調用Sleep,可使線程自願放棄它剩余的時間片。 • 系統將在大約的指定毫秒數內使線程不可調度。不錯,如果告訴系統,想睡眠100ms,那麽可以睡眠大約這麽長時間,但是也可能睡眠數秒鍾或者數分鍾。還是那個反複重申的概念, Windows不是個實時操作系統。雖然線程可能在規定的時間被喚醒,但是它能否做到,取決于系統 中還有什麽操作正在進行。 • 可以調用Sleep,並且爲cMilliseconds)參數傳遞INFINITE。這將告訴系統永遠不要調度該線程。這不是一件值得去做的事情。最好是讓線程退出,並還原它的堆棧和內核對象。 • 可以將0傳遞給Sleep。這將告訴系統,調用線程將釋放剩余的時間片,並迫使系統調度另一個線程。但是,系統可以對剛剛調用Sleep的線程重新調度。如果不存在多個擁有相同優先級的可調度線程,就會出現這種情況。Sleep(0)是一個非常有意思的方法。要小心Sleep()神秘的時間調整問題。Sleep()可能會使你的機器出現特別的問題。這種問題在另一台機器上可能無法再現。 切換到另一個線程 系統提供了一個稱爲SwitchToThread的函數,使得另一個可調度線程(如果存在能夠運行)。當調用這個函數的時候,系統要查看是否存在一個迫切需要C P U時間的線程。如果沒有線程迫切需要C P U時間SwitchToThread就會立即返回。如果存在一個迫切需要C P U時間的線程,SwitchToThread就對該線程進行調度(該線程的優先級可能低于調用SwitchToThread的線程)。這個迫切需要C P U時間的線程可以運行一個時間段,然後系統調度程序照常運行。該函數允許一個需要資源的線程強制另一個優先級較低、而目前卻擁有該資源的線程放棄該資源。如果調用SwitchToThread函數時沒有其他線程能夠運行,那麽該函數返回FALSE,否則返回一個非0值。調用SwitchToThread函數與調用Sleep是相似的,差別是SwitchToThread允許優先級較低的線程運行。即使低優先級線程迫切需要CPU時間,而Sleep則可能因爲優先級關系使得剛放棄CPU的線程被立即重新調度。 優先級 操作系統會負責爲每個線程分配CPU時間。一個線程所分配到的CPU時間主要取決于該線程的優先級,而線程的優先級又取決于進程的優先級類和線程本身的相對優先級。 1. 進程的優先級類 進程的優先級類用來描述一個進程的優先程度。Win32支持四種不同的優先級類: Idle、Normal、High 和Realtime。其中,Normal是默認的優先級。在Windows單元中,每一種優先級類都對應著一個標志。當要進行進程的優先級設置時,可以用一種優先級類與CreateProcess()的參數dwCreationFlags進行或操作。另外,還可以動態地爲一個已有的進程調整優先級類。這時候,通常你要用到下面API函數 bool SetPriorityClass(HANDLE hProcess,DWORD fdwPriority),其中第一個參數是進程的句柄,你可以通過GetCurrentProcess來獲得當前進程的句柄。每個優先級類也對應一個數字,值在4~ 24之間。注意在Windows NT/2000下,要有特殊的權限才能修改進程的優先類。默認的設置允許進程設置它們的優先級類,但是,這些都可以由系統管理員來關閉,尤其是在高負載的WinNT/2000服務器上。 大多數情況下,進程的優先級類不要被設爲Realtime。因爲,大多數操作系統本身的線程的優先級類比Realtime低。如果一個進程得到的C P U時間比操作系統本身還多,後果是無法想象的。即使將進程的優先級類設爲High ,也可能引起問題。因爲,當高優先級的線程沒有大部分空時間或等待外部事件時,它要從低優先級的線程和進程中搶奪CPU時間,直到它被一事件阻塞或處于空閑狀態或處理消息。所以,在搶占式多任務操作系統中如果不能合理地安排優先級,就很容易崩潰。 優先級類 說明 實時 進程中的線程必須立即對事件作出響應,以便執行關鍵時間的任務。 該進程中的線程還會搶先于操作系統組件之前運行。使用本優先級類 時必須極端小心 高 進程中的線程必須立即對事件作出響應,以便執行關鍵時間的任務。 Task Manager(任務管理器)在這個類上運行,以便用戶可以撤消脫 離控制的進程 高于正常 進程中的線程在正常優先級與高優先級之間運行(這是Wi n d o w s 2 0 0 0中的新優先級類) 正常 進程中的線程沒有特殊的調度需求 低于正常 進程中的線程在正常優先級與空閑優先級之間運行(這是Wi n d o w s 2 0 0 0中的新優先級類) 空閑 進程中的線程在系統空閑時運行。該進程通常由屏幕保護程序或後 台實用程序和搜集統計數據的軟件使用 2. 相對優先級 決定一個線程全面的優先級的另一方面是相對優先級。優先級類是針對進程的,它對進程內部的 所有線程都有效。而相對優先級是針對某個線程的。一個線程的相對優先級可設爲以下七種: Idle、Lowest、Below Normal、Normal、Above Normal、Highest 和Time Critical。 要設置一個線程的相對優先級,可以通過API函數SetThreadPriority來完成,再DELPHI中,你可以通過TThread對象的Priority屬性來設置。獲得線程相對優先級的API函數是int GetThreadPriority(HANDLE hThread); 系統何如根據優先級來調度線程 每個線程都會被賦予一個從0(最低)到31(最高)的優先級號碼。當系統確定將哪個線程分配給CPU時,它首先觀察優先級爲31的線程,並以循環方式對它們進行調度。如果優先級爲31的線程可以調度,那麽就將該線程賦予一個CPU。在該線程的時間片結束時,系統要查看是否還有另一個優先級爲31的線程可以運行,如果有,它將允許該線程被賦予一個CPU。只要優先級爲31的線程是可調度的,系統就絕對不會將優先級爲0到30的線程分配給C P U。這種情況稱爲渴求調度(starvation)。當高優先級線程使用大量的CPU時間,從而使得低優先級線程無法運行時,便會出現渴求情況。在多處理器計算機上出現渴求情況的可能性要少得多,因爲在這樣的計算機上,優先級爲31和優先級爲30的線程能夠同時運行。系統總是設法使CPU保持繁忙狀態,只有當沒有線程可以調度的時候, CPU才處于空閑狀態。 人們可能認爲,在這樣的系統中,低優先級線程永遠得不到機會運行。不過正像前面指出的那樣,在任何一個時段內,系統中的大多數線程是不能調度的。例如,如果進程的主線程調用GetMessage函數,而系統發現沒有線程可以供它使用,那麽系統就暫停進程的線程運行,釋放該線程的剩余時間片,並且立即將CPU分配給另一個等待運行的線程。如果沒有爲GetMessage函數顯示可供檢索的消息,那麽進程的線程將保持暫停狀態,並且決不會被分配給CPU。但是,當消息被置于線程的隊列中時,系統就知道該線程不應該再處于暫停狀態。此時,如果沒有更高優先級的線程需要運行,系統就將該線程分配給一個CPU。 高優先級線程將搶在低優先級線程之前運行,不管低優先級線程正在運行什麽。例如,如果一個優先級爲5的線程正在運行,系統發現一個高優先級的線程准備要運行,那麽系統就會立即暫停低優先級線程的運行(即使它處于它的時間片中),並且將C P U分配給高優先級線程,使它獲得一個完整的時間片。還有,當系統引導時,它會創建一個特殊的線程,稱爲0頁線程。該線程被賦予優先級0,它是整個系統中唯一的一個在優先級0上運行的線程。當系統中沒有任何線程需要執行操作時,0頁線程負責將系統中的所有空閑R A M頁面置0。 動態提高線程的優先級等級 通過將線程的相對優先級與線程的進程優先級類綜合起來考慮,系統就可以確定線程的優先級等級。有時這稱爲線程的基本優先級等級。 系統常常要提高線程的優先級等級,以便對窗口消息或讀取磁盤等I/O事件作出響應。 例如,在高優先級類進程中的一個正常優先級等級的線程的基本優先級等級是13。如果用戶按下一個操作鍵,系統就會將一個WM_KEYDOWN消息放入線程的隊列中。由于一個消息已經出現在線程的隊列中,因此該線程就是可調度的線程。此外,鍵盤設備驅動程序也能夠告訴系統暫時提高線程的優先級等級。該線程的優先級等級可能提高2級,其當前優先級等級改爲15。系統在優先級爲15時爲一個時間片對該線程進行調度。一旦該時間片結束,系統便將線程的優先級遞減1,使下一個時間片的線程優先級降爲14。該線程的第三個時間片按優先級等級13來執行。如果線程要求執行更多的時間片,均按它的基本優先級等級13來執行。注意,線程的當前優先級等級決不會低于線程的基本優先級等級。此外,導致線程成爲可調度線程的設備驅動程序可以決定優先級等級提高的數量。Microsoft並沒有規定各個設備驅動程序可以給線程的優先級提高多少個等級。這樣就使得Microsoft可以不斷地調整線程優先級提高的動態等級,以確定最佳的總體響應性能。系統只能爲基本優先級等級在1至15之間的線程提高其優先級等級。實際上這是因爲這個範圍稱爲動態優先級範圍。此外,系統決不會將線程的優先級等級提高到實時範圍(高于15)。由于實時範圍中的線程能夠執行大多數操作系統的函數,因此給等級的提高規定一個範圍,就可以防止應用程序幹擾操作系統的運行。另外,系統決不會動態提高實時範圍內的線程優先級等級。 另一種情況也會導致系統動態地提高線程的優先級等級。比如有一個優先級爲4的線程准備運行但是卻不能運行,因爲一個優先級爲8的線程正連續被調度。在這種情況下,優先級爲4的線程就非常渴望得到CPU時間。當系統發現一個線程在大約3至4s內一直渴望得到C P U時間,它就將這個渴望得到CPU時間的線程的優先級動態提高到15,並讓該線程運行兩倍于它的時間量。當到了兩倍時間量的時候,該線程的優先級立即返回到它的基本優先級。 系統動態的改變優先級,在我們編程的時候會産生不良影響,爲此,還有兩個API函數可以使得系統的此功能不起作用。 bool SetProcessPriorityBoost(HANDLE hProcess,bool disablePriorityboost); bool SetThreadPriorityBoost(HANDLE hThread,bool disablePriorityboost); 從名字你就應該可以看出,第一個API函數可以激活或停用指定進程所有線程的優先級提高功能,而後面一個則是針對特定線程的。 例子:關鍵的代碼如下 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedBlockStart.gif[/img][/url][url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedBlock.gif[/img][/url]...{ [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif[/img][/url] 作者:wudi_1982 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif[/img][/url] 聯系方式:wudi_1982@hotmail.com [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif[/img][/url] 轉載請著名出處 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/InBlock.gif[/img][/url] 本代碼旨在演示線程的調度,很多位置沒有加入適當的控制和資源釋放,請按照後續操作執行 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedBlockEnd.gif[/img][/url]} [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]type [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] TSleepType=(stSleep,stSwitch); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] //演示線程調度的TThread派生類 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] TPriThread1=class(TThread) [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] private [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] CurCount : integer; //當前計數 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] Flb : TLabel; //用來顯示當前計數的label [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] FCanSleep : Boolean; //是否自動釋放時間片 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] FSleepMs : integer; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] FSleepType : TSleepType;//釋放時間片的方式 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] procedure GetRestult; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] protected [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] procedure Execute;override; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] public [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] constructor Create(CreateSuspended: Boolean;ALabel : TLabel); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] property CanSleep : boolean read FCanSleep write FCanSleep; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] property SleepMs : integer read FSleepMs write FSleepMs; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] property SleepType : TSleepType read FSleepType write FSleepType; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url].... [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedBlockStart.gif[/img][/url][url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedBlock.gif[/img][/url]...{ TPriThread1的實現 } [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]constructor TPriThread1.Create(CreateSuspended: Boolean; ALabel: TLabel); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]begin [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]//構造函數 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] flb := ALabel; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] FSleepMs := 0; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] FCanSleep := true; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] FSleepType := stSleep; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] inherited create(CreateSuspended); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]procedure TPriThread1.Execute; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]var [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] i : integer; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]begin [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] inherited; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] FreeOnTerminate := true; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] CurCount := 0; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] for i := 0 to 100000 do [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] begin [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] CurCount := i;//改變當前計數 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] Synchronize(GetRestult);//顯示結果 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] if FCanSleep then//是否自動釋放時間片 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] begin [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] //根據釋放時間片的不同方式進行相應操作 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] case FSleepType of [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] stSleep : Sleep(SleepMs);//睡眠 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] stSwitch : SwitchToThread;//調用其他線程 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]procedure TPriThread1.GetRestult; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]begin [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] flb.Caption := IntToStr(CurCount); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ExpandedBlockStart.gif[/img][/url][url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/ContractedBlock.gif[/img][/url]...{Form1的主要代碼} [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]procedure TForm1.btnPThread1CreateClick(Sender: TObject); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]begin [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] //生成兩個線程 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] MyPThread1 := TPriThread1.Create( not ckbx1State.Checked,lab1); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] MyPThread2 := TPriThread1.Create(not ckbx2State.Checked,lab2); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] //得到他們當前的優先級 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] lb1p.Caption := inttostr(GetThreadPriority(MyPThread1.Handle)); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] lb2p.Caption := inttostr(GetThreadPriority(MyPThread2.Handle)); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]procedure TForm1.btnPThread1ResClick(Sender: TObject); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]begin [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] //執行線程 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] MyPThread1.Resume; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] ckbx1State.Checked := true; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] MyPThread2.Resume; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] ckbx2State.Checked := true; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]procedure TForm1.btnPThread1SudClick(Sender: TObject); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]begin [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] //挂起線程 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] MyPThread1.Suspend; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] ckbx1State.Checked := false; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] MyPThread2.Suspend; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] ckbx2State.Checked := false; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]procedure TForm1.btnUpPThread1Click(Sender: TObject); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]begin [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] //在線程挂起時,提高第一個線程的相對優先級 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] MyPThread1.Priority := tpHigher; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] //顯示當前的優先級到屏幕 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] lb1p.Caption := inttostr(GetThreadPriority(MyPThread1.Handle)); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] // MyPThread2.Priority := tpHigher; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]procedure TForm1.btnUpdateSleepClick(Sender: TObject); [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]begin [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] //修改兩個線程的時間片釋放方式 [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] MyPThread1.CanSleep := ckbxAllowSleep1.Checked; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] case RadioGroup1.ItemIndex of [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] 0 : MyPThread1.SleepType := stSleep; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] 1 : MyPThread1.SleepType := stSwitch; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] MyPThread2.CanSleep := ckbxAllowSleep2.Checked; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] case RadioGroup2.ItemIndex of [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] 0 : MyPThread2.SleepType := stSleep; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] 1 : MyPThread2.SleepType := stSwitch; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url]end; [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] [url=/bbs/detail_566835.html][img]http://images.csdn.net/syntaxhighlighting/OutliningIndicators/None.gif[/img][/url] 窗體效果: [url=/bbs/detail_566835.html][img]http://p.blog.csdn.net/images/p_blog_csdn_net/wudi_1982/線程調度程序演示界面.JPG[/img][/url] 讓我們來用這個程序測試一些效果: 1、基本執行。程序運行之後,使用默認設置,點擊【創建線程】按鈕,線程將被創建,並且挂起,這是你可以間隔的點擊【執行線程】和【挂起線程】按鈕,你會在屏幕上看到線程的當前計數,注意這兩個計數之間的差值,以及整個界面的執行效果(我指的是在你讓線程不斷的執行和挂起之間界面是否會出現不刷新的情況),當線程執行完畢之後,關閉程序。 2、通過Sleep(0)釋放時間片演示線程調度。運行程序,使用默認設置,點擊【創建線程】按鈕,然後將兩個線程的自釋放時間片功能統統去掉(也就是去掉ckbxAllowSleep1 and 2的勾勾),然後點擊【修改睡眠方式】按鈕,隨後你可以進行間隔點擊【執行線程】和【挂起線程】按鈕,多做幾次這樣的操作,觀察兩個計數之間的差值,和測試1的差值比較一下。我想你應該能想到些什麽。然後,幾乎可以肯定你的界面將會出現無法刷新的情況,並且你的鼠標無法立即在此界面上進行其他的操作。這個時候,稍等一下,你會發現過了一會兒,兩個當前計數都被刷新了。爲什麽??這時,我們除了考慮我們創建的兩個線程之外,你還要考慮的你程序本身的主線程以及其他可能存在的附屬線程,我們再去程序中線程的那段循環代碼, CurCount := i;//改變當前計數 Synchronize(GetRestult);//顯示結果 if FCanSleep then//是否自動釋放時間片 begin //根據釋放時間片的不同方式進行相應操作 case FSleepType of stSleep : Sleep(SleepMs);//睡眠 stSwitch : SwitchToThread;//調用其他線程 end; end; 你應該看到線程將當前計數顯示在屏幕上的操作是執行了Synchronize(GetRestult),這裏,因爲我們的線程和VCL界面發生了交互,我們必須對Synchronize有所了解,去看VCL的源碼,你會發現,當你在程序中第一次創建一個附屬線程時, VCL將會從主線程環境中創建和維護一個隱含的線程窗口。此窗口唯一的目的是把通過Synchronize()調用的方法排隊。Synchronize()把由Method參數傳遞過來的方法保存在TThread的FMethod字段中,然後,給線程窗口發一個CM_EXECPROC消息,並且把消息的lParam參數設爲self(這裏指線程對象)。當線程窗口的窗口過程收到這個消息後,它就調用FMethod字段所指定的方法。由于線程窗口是在主線程內創建的,線程窗口的窗口過程也將被主線程執行。因此,FMethod字段所指定的方法就在主線程內執行。 在我們選擇釋放時間片的模式下,在這裏,無論我們是用Sleep還是SwitchToThread,當前線程都會立即釋放時間片,因爲這時我們並沒有修改線程的優先級,他們都在同樣的優先級環境下運行,那麽當占用CPU的線程釋放時間片後,其他線程將可以相對輕松的得到CPU,所以在使用釋放時間片的模式下,界面的刷新會良好。並且調度相對有序。 3、Sleep和SwitchToThread區別的演示。運行程序,使用默認設置,點擊【創建線程】按鈕,然後點擊【提高線程1的優先級】按鈕,再點擊【執行線程】這是,兩個線程將不再是同樣的優先級,其他設置依然是默認的(使用sleep方式釋放時間片),你會看到線程1首先執行,線程2處于可調度模式,但並沒有被調度(當前計數沒有刷新),並且屏幕也不刷新,在稍等一段時間之後,屏幕刷新,線程2也開始運行,並且此時屏幕刷新正常。爲什麽呢?回頭去看本文上面的內容,當線程1的優先級提高之後,系統會首先調度它,雖然它使用sleep(0)來釋放時間片,但當時間片釋放後,因爲它的優先級相對較高,系統依然會調度線程1,所以此時,線程2將不能執行,界面也不能有效刷新。在這個思路下,再做一個測試,使用同樣的方式,只不過這次,在線程執行之前,除了提高線程1的優先級之外,還將線程1釋放時間片方式改爲SwitchToThread,此時你就可以看到兩個線程都有機會執行,並且界面也將有效刷新。 4、你還可以做其他配置信息的測試,相信會加深對WIN32平台下線程調度的了解。 參考文獻 1、《DELPHI5開發人員指南》 2、《WINDOWS核心編程》 注:以上程序在D7+WINXP下測試通過 轉載請著名出處。
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 
 熱帖排行
 
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有