delphi 線程教學第五節:多個線程同時執行相同的任務


第五節:多個線程同時執行相同的任務
 
1.鎖
 
設,有一個房間 X ,X為全局變量,它有兩個函數  X.Lock 與 X.UnLock;
有如下代碼:
 
X . Lock;  
    訪問資源 P;
X . Unlock;
 
現在有A,B兩個線程時空都要執行此段代碼。
當線程A執行了 X.Lock 之后,在沒有執行完  X.Unlock 之前,第二個線程B此時也來執行 X.Lock ,
線程B就會阻塞在 X.Lock 這句代碼上。我們可以認為,此時,線程A進入房間,其它線程不准再進入房間。
只能在外面等,直到線程A執行完 X.Unlock 后,線程A退出了房間,此時線程B才可以進入。
線程B進入了房間后,其它線程此時同樣不准再進入。
 
即: 多個線程用本段代碼“訪問資源P”的操作是排隊執行的。
 
2.  TMonitor
 
在 delphi XE2 及以后的版本中,提供了一個方便的鎖功能。TMonitor。
它是一個Record, TMonitor.Enter(X); 與 TMoniter.Exit(X); 等效於上面 lock 與 unlock;
X 可以是任何一個 TObject 實例。
 
本例源碼下載(delphi XE8版本): FooMuliThread.zip
 
unit  uCountThread; 
interface 
uses
   uFooThread; 
type
   TCountThread =  class ;
   TOnGetNum =  function (Sender: TCountThread):  boolean  of  object //獲取 Num 事件。
   TOnCounted =  procedure (Sender: TCountThread)  of  object ;
   TCountThread =  class (TFooThread)
   private
     procedure  Count;
     procedure  DoOnCounted;
     function  DoOnGetNum:  boolean ;
   public
     procedure  StartThread; override;
   public
     Num:  integer ;
     Total:  integer ;
     OnCounted: TOnCounted;
     OnGetNum: TOnGetNum;
     ThreadName:  string ;
   end ;
 
implementation
 
{ TCountThread }
 
procedure  TCountThread . Count;
var
   i:  integer ;
begin
 
   // 注意多線程不適合打斷點調試。
   // 因為一旦在 IDE 中斷后,狀態全亂了。
   // 可以寫 Log 或用腦袋想,哈哈。
 
   if  DoOnGetNum  then  // 獲取參數 Num
   begin
     Total :=  0 ;
     if  Num >  0  then
       for  i :=  1  to  Num  do
       begin
         Total := Total + i;
         sleep( 5 );  //嫌慢就刪掉此句。
       end ;
     DoOnCounted;  // 引發 OnCounted 事件,告知調用者。
     ExecProcInThread(Count);  // 上節說到在線程時空里執行本句。
   end ;
 
end ;
 
procedure  TCountThread . DoOnCounted;
begin
   if  Assigned(OnCounted)  then
     OnCounted(self);
end ;
 
function  TCountThread . DoOnGetNum:  boolean ;
begin
   result :=  false ;
   if  Assigned(OnGetNum)  then
     result := OnGetNum(self);
end ;
 
procedure  TCountThread . StartThread;
begin
   inherited ;
   ExecProcInThread(Count);  // 把 Count 過程塞到線程中運行。
end ;
 
end .
 
unit  uFrmMain;
interface
uses
   Winapi . Windows, Winapi . Messages, System . SysUtils, System . Variants, System . Classes,  Graphics,
   Vcl . Controls, Vcl . Forms, Vcl . Dialogs, Vcl . StdCtrls, uCountThread;
 
type
   TFrmMain =  class (TForm)
     memMsg: TMemo;
     edtNum: TEdit;
     btnWork: TButton;
     lblInfo: TLabel;
     procedure  FormCreate(Sender: TObject);
     procedure  FormDestroy(Sender: TObject);
     procedure  btnWorkClick(Sender: TObject);
     procedure  FormCloseQuery(Sender: TObject;  var  CanClose:  Boolean );
   private
     { Private declarations }
     FCo1, FCo2, FCo3: TCountThread;  // 定義了3個線程實例
     // 以后章節將講解采用 List 容器來裝線程實例。
     FBuff: TStringList;
     FBuffIndex:  integer ;
     FBuffMaxIndex:  integer ;
     FWorkedCount:  integer ;
     procedure  DispMsg(AMsg:  string );
     procedure  OnThreadMsg(AMsg:  string );
 
     function  OnGetNum(Sender: TCountThread):  Boolean ;
     procedure  OnCounted(Sender: TCountThread);
 
     procedure  LockBuffer;
     procedure  UnlockBuffer;
 
     procedure  LockCount;
     procedure  UnlockCount;
 
   public
     { Public declarations }
   end ;
 
var
   FrmMain: TFrmMain;
 
implementation
 
{ $R  *.dfm}
{ TFrmMain }
 
procedure  TFrmMain . btnWorkClick(Sender: TObject);
var
   s:  string ;
begin
 
   btnWork . Enabled :=  false ;
   FWorkedCount :=  0 ;
   FBuffIndex :=  0 ;
   FBuffMaxIndex := FBuff . Count -  1 ;
 
   s :=  '共'  + IntToStr(FBuffMaxIndex +  1 ) +  '個任務,已完成:'  + IntToStr(FWorkedCount);
   lblInfo . Caption := s;
 
   FCo1 . StartThread;
   FCo2 . StartThread;
   FCo3 . StartThread;
 
end ;
 
procedure  TFrmMain . DispMsg(AMsg:  string );
begin
   memMsg . Lines . Add(AMsg);
end ;
 
procedure  TFrmMain . FormCloseQuery(Sender: TObject;  var  CanClose:  Boolean );
begin
   // 防止計算期間退出
   LockCount;  // 請思考,這里為什么要用 LockCount;
   CanClose := btnWork . Enabled;
   if  not  btnWork . Enabled  then
     DispMsg( '正在計算,不准退出!' );
   UnlockCount;
end ;
 
procedure  TFrmMain . FormCreate(Sender: TObject);
begin
 
   FCo1 := TCountThread . Create( false );
   FCo1 . OnStatusMsg := self . OnThreadMsg;
   FCo1 . OnGetNum := self . OnGetNum;
   FCo1 . OnCounted := self . OnCounted;
   FCo1 . ThreadName :=  '線程1' ;
 
   FCo2 := TCountThread . Create( false );
   FCo2 . OnStatusMsg := self . OnThreadMsg;
   FCo2 . OnGetNum := self . OnGetNum;
   FCo2 . OnCounted := self . OnCounted;
   FCo2 . ThreadName :=  '線程2' ;
 
   FCo3 := TCountThread . Create( false );
   FCo3 . OnStatusMsg := self . OnThreadMsg;
   FCo3 . OnGetNum := self . OnGetNum;
   FCo3 . OnCounted := self . OnCounted;
   FCo3 . ThreadName :=  '線程3' ;
 
   FBuff := TStringList . Create;
 
   // 構造一組數據用來測試
 
   FBuff . Add( '100' );
   FBuff . Add( '136' );
   FBuff . Add( '306' );
   FBuff . Add( '156' );
   FBuff . Add( '152' );
   FBuff . Add( '106' );
   FBuff . Add( '306' );
   FBuff . Add( '156' );
   FBuff . Add( '655' );
   FBuff . Add( '53' );
   FBuff . Add( '99' );
   FBuff . Add( '157' );
 
end ;
 
procedure  TFrmMain . FormDestroy(Sender: TObject);
begin
   FCo1 . Free;
   FCo2 . Free;
   FCo3 . Free;
end ;
 
procedure  TFrmMain . LockBuffer;
begin
   System . TMonitor . Enter(FBuff);
   // System 是單元名。因為 TMonitor 在 Forms 中也有一個相同的名字。
   // 同名的類與函數,就要在前面加單元名稱以示區別。
end ;
 
procedure  TFrmMain . LockCount;
begin
   // 任意一個 TObject 就行,所以我用了 btnWork
   System . TMonitor . Enter(btnWork);
end ;
 
procedure  TFrmMain . OnCounted(Sender: TCountThread);
var
   s:  string ;
begin
 
   LockCount;  // 此處亦可以用 LockBuffer
   // 但是,鎖不同的對象,宜用不同的鎖。
   // 每把鎖的功能要單一,鎖的粒度要最小化。才能提高效率。
 
   s := Sender . ThreadName +  ':'  + IntToStr(Sender . Num) +  '累加和為:' ;
   s := s + IntToStr(Sender . Total);
   OnThreadMsg(s);
 
   inc(FWorkedCount);
 
   s :=  '共'  + IntToStr(FBuffMaxIndex +  1 ) +  '個任務,已完成:'  + IntToStr(FWorkedCount);
 
   TThread . Synchronize( nil ,
     procedure
     begin
       lblInfo . Caption := s;
     end );
 
   if  FWorkedCount >= FBuffMaxIndex +  1  then
   begin
     TThread . Synchronize( nil ,
       procedure
       begin
         DispMsg( '已計算完成' );
         btnWork . Enabled :=  true // 恢復按鈕狀態。
       end );
   end ;
 
   UnlockCount;
 
end ;
 
function  TFrmMain . OnGetNum(Sender: TCountThread):  Boolean ;
begin
   LockBuffer;  // 將多個線程訪問 FBuff 排隊。
   try
     if  FBuffIndex > FBuffMaxIndex  then
     begin
       result :=  false ;
     end
     else
     begin
       Sender . Num := StrToInt(FBuff[FBuffIndex]);
       result :=  true ;
       inc(FBuffIndex);
     end ;
   finally
     UnlockBuffer;
   end ;
end ;
 
procedure  TFrmMain . OnThreadMsg(AMsg:  string );
begin
   TThread . Synchronize( nil ,
     procedure
     begin
       DispMsg(AMsg);
     end );
end ;
 
procedure  TFrmMain . UnlockBuffer;
begin
   System . TMonitor . Exit(FBuff);
end ;
 
procedure  TFrmMain . UnlockCount;
begin
   System . TMonitor . Exit(btnWork);
end ;
 
end .
 
下一節,我們將學習 List 與泛型。為以后設計其它的更高級與靈活的線程做准備。
 
 
 



免責聲明!

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



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