a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 編程 > Delphi > 正文

Delphi中的線程類--之(4)

2019-11-18 18:28:02
字體:
來源:轉載
供稿:網友
 

Delphi中的線程類

 

猛禽[Mental Studio]

http://mental.mentsu.com

 

之四

臨界區(CriticalSection)則是一項共享數據訪問保護的技術。它其實也是相當于一個全局的布爾變量。但對它的操作有所不同,它只有兩個操作:EnterLeave,同樣可以把它的兩個狀態當作TrueFalse,分別表示現在是否處于臨界區中。這兩個操作也是原語,所以它可以用于在多線程應用中保護共享數據,防止訪問沖突。

用臨界區保護共享數據的方法很簡單:在每次要訪問共享數據之前調用Enter設置進入臨界區標志,然后再操作數據,最后調用Leave離開臨界區。它的保護原理是這樣的:當一個線程進入臨界區后,如果此時另一個線程也要訪問這個數據,則它會在調用Enter時,發現已經有線程進入臨界區,然后此線程就會被掛起,等待當前在臨界區的線程調用Leave離開臨界區,當另一個線程完成操作,調用Leave離開后,此線程就會被喚醒,并設置臨界區標志,開始操作數據,這樣就防止了訪問沖突。

以前面那個InterlockedIncrement為例,我們用CriticalSectionWindows API)來實現它:

Var

  InterlockedCrit : TRTLCriticalSection;

PRocedure InterlockedIncrement( var aValue : Integer );

Begin

  EnterCriticalSection( InterlockedCrit );

  Inc( aValue );

  LeaveCriticalSection( InterlockedCrit );

End;

現在再來看前面那個例子:

1.         線程A進入臨界區(假設數據為3

2.         線程B進入臨界區,因為A已經在臨界區中,所以B被掛起

3.         線程A對數據加一(現在是4

4.         線程A離開臨界區,喚醒線程B(現在內存中的數據是4

5.         線程B被喚醒,對數據加一(現在就是5了)

6.         線程B離開臨界區,現在的數據就是正確的了。

臨界區就是這樣保護共享數據的訪問。

關于臨界區的使用,有一點要注意:即數據訪問時的異常情況處理。因為如果在數據操作時發生異常,將導致Leave操作沒有被執行,結果將使本應被喚醒的線程未被喚醒,可能造成程序的沒有響應。所以一般來說,如下面這樣使用臨界區才是正確的做法:

EnterCriticalSection

Try

   //  操作臨界區數據

Finally

  LeaveCriticalSection

End;

 

最后要說明的是,EventCriticalSection都是操作系統資源,使用前都需要創建,使用完后也同樣需要釋放。如TThread類用到的一個全局EventSyncEvent和全局CriticalSectionTheadLock,都是在InitThreadSynchronizationDoneThreadSynchronization中進行創建和釋放的,而它們則是在Classes單元的InitializationFinalization中被調用的。

由于在TThread中都是用API來操作EventCriticalSection的,所以前面都是以API為例,其實Delphi已經提供了對它們的封裝,在SyncObjs單元中,分別是TEvent類和TCriticalSection類。用法也與前面用API的方法相差無幾。因為TEvent的構造函數參數過多,為了簡單起見,Delphi還提供了一個用默認參數初始化的Event類:TSimpleEvent

順便再介紹一下另一個用于線程同步的類:TMultiReadExclusiveWriteSynchronizer,它是在SysUtils單元中定義的。據我所知,這是Delphi RTL中定義的最長的一個類名,還好它有一個短的別名:TMREWSync。至于它的用處,我想光看名字就可以知道了,我也就不多說了。

 

有了前面對EventCriticalSection的準備知識,可以正式開始討論SynchronizeWaitFor了。

 

我們知道,Synchronize是通過將部分代碼放到主線程中執行來實現線程同步的,因為在一個進程中,只有一個主線程。先來看看Synchronize的實現:

procedure TThread.Synchronize(Method: TThreadMethod);

begin

  FSynchronize.FThread := Self;

  FSynchronize.FSynchronizeException := nil;

  FSynchronize.FMethod := Method;

  Synchronize(@FSynchronize);

end;

其中FSynchronize是一個記錄類型:

  PSynchronizeRecord = ^TSynchronizeRecord;

  TSynchronizeRecord = record

    FThread: TObject;

    FMethod: TThreadMethod;

    FSynchronizeException: TObject;

  end;

用于進行線程和主線程之間進行數據交換,包括傳入線程類對象,同步方法及發生的異常。

Synchronize中調用了它的一個重載版本,而且這個重載版本比較特別,它是一個“類方法”。所謂類方法,是一種特殊的類成員方法,它的調用并不需要創建類實例,而是像構造函數那樣,通過類名調用。之所以會用類方法來實現它,是因為為了可以在線程對象沒有創建時也能調用它。不過實際中是用它的另一個重載版本(也是類方法)和另一個類方法StaticSynchronize。下面是這個Synchronize的代碼:

class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);

var

  SyncProc: TSyncProc;

begin

  if GetCurrentThreadID = MainThreadID then

    ASyncRec.FMethod

  else

  begin

    SyncProc.Signal := CreateEvent(nil, True, False, nil);

    try

      EnterCriticalSection(ThreadLock);

      try

        if SyncList = nil then

          SyncList := TList.Create;

        SyncProc.SyncRec := ASyncRec;

        SyncList.Add(@SyncProc);

        SignalSyncEvent;

        if Assigned(WakeMainThread) then

          WakeMainThread(SyncProc.SyncRec.FThread);

        LeaveCriticalSection(ThreadLock);

        try

          WaitForSingleObject(SyncProc.Signal, INFINITE);

        finally

          EnterCriticalSection(ThreadLock);

        end;

      finally

        LeaveCriticalSection(ThreadLock);

      end;

    finally

      CloseHandle(SyncProc.Signal);

    end;

    if Assigned(ASyncRec.FSynchronizeException) then raise ASyncRec.FSynchronizeException;

  end;

end;

這段代碼略多一些,不過也不算太復雜。

首先是判斷當前線程是否是主線程,如果是,則簡單地執行同步方法后返回。

如果不是主線程,則準備開始同步過程。

通過局部變量SyncProc記錄線程交換數據(參數)和一個Event Handle,其記錄結構如下:

  TSyncProc = record

    SyncRec: PSynchronizeRecord;

    Signal: THandle;

  end;

然后創建一個Event,接著進入臨界區(通過全局變量ThreadLock進行,因為同時只能有一個線程進入Synchronize狀態,所以可以用全局變量記錄),然后就是把這個記錄數據存入SyncList這個列表中(如果這個列表不存在的話,則創建它)。可見ThreadLock這個臨界區就是為了保護對SyncList的訪問,這一點在后面介紹CheckSynchronize時會再次看到。

再接下就是調用SignalSyncEvent,其代碼在前面介紹TThread的構造函數時已經介紹過了,它的功能就是簡單地將SyncEvent作一個Set的操作。關于這個SyncEvent的用途,將在后面介紹WaitFor時再詳述。

接下來就是最主要的部分了:調用WakeMainThread事件進行同步操作。WakeMainThread是一個TNotifyEvent類型的全局事件。這里之所以要用事件進行處理,是因為Synchronize方法本質上是通過消息,將需要同步的過程放到主線程中執行,如果在一些沒有消息循環的應用中(如ConsoleDLL)是無法使用的,所以要使用這個事件進行處理。

而響應這個事件的是application對象,下面兩個方法分別用于設置和清空WakeMainThread事件的響應(來自Forms單元):

procedure TApplication.HookSynchronizeWakeup;

begin

  Classes.WakeMainThread := WakeMainThread;

end;

 

procedure TApplication.UnhookSynchronizeWakeup;

begin

  Classes.WakeMainThread := nil;

end;

上面兩個方法分別是在TApplication類的構造函數和析構函數中被調用。

這就是在Application對象中WakeMainThread事件響應的代碼,消息就是在這里被發出的,它利用了一個空消息來實現:

procedure TApplication.WakeMainThread(Sender: TObject);

begin

  PostMessage(Handle, WM_NULL, 0, 0);

end;

而這個消息的響應也是在Application對象中,見下面的代碼(刪除無關的部分):

procedure TApplication.WndProc(var Message: TMessage);

begin

  try

    with Message do

      case Msg of

        WM_NULL:

          CheckSynchronize;

  except

    HandleException(Self);

  end;

end;

其中的CheckSynchronize也是定義在Classes單元中的,由于它比較復雜,暫時不詳細說明,只要知道它是具體處理Synchronize功能的部分就好,現在繼續分析Synchronize的代碼。

在執行完WakeMainThread事件后,就退出臨界區,然后調用WaitForSingleObject開始等待在進入臨界區前創建的那個Event。這個Event的功能是等待這個同步方法的執行結束,關于這點,在后面分析CheckSynchronize時會再說明。

注意在WaitForSingleObject之后又重新進入臨界區,但沒有做任何事就退出了,似乎沒有意義,但這是必須的!

因為臨界區的EnterLeave必須嚴格的一一對應。那么是否可以改成這樣呢:

        if Assigned(WakeMainThread) then

          WakeMainThread(SyncProc.SyncRec.FThread);

        WaitForSingleObject(SyncProc.Signal, INFINITE);

      finally

        LeaveCriticalSection(ThreadLock);

      end;

上面的代碼和原來的代碼最大的區別在于把WaitForSingleObject也納入臨界區的限制中了。看上去沒什么影響,還使代碼大大簡化了,但真的可以嗎?

事實上是不行!

因為我們知道,在Enter臨界區后,如果別的線程要再進入,則會被掛起。而WaitFor方法則會掛起當前線程,直到等待別的線程SetEvent后才會被喚醒。如果改成上面那樣的代碼的話,如果那個SetEvent的線程也需要進入臨界區的話,死鎖(Deadlock)就發生了(關于死鎖的理論,請自行參考操作系統原理方面的資料)。

死鎖是線程同步中最需要注意的方面之一!

最后釋放開始時創建的Event,如果被同步的方法返回異常的話,還會在這里再次拋出異常。

(待續)


上一篇:Delphi中的線程類--之(5,大結局)

下一篇:Delphi中的線程類--之(3)

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
學習交流
熱門圖片

新聞熱點

疑難解答

圖片精選

網友關注

主站蜘蛛池模板: 婷婷国产成人精品视频 | 欧美精品tv | 欧美在线免费 | 亚洲午夜性视频 | 国产成人精品一区二区视频免费 | 成人亚洲精品 | 黄色片在线播放 | 国产综合久久 | 日本一区二区在线视频 | 9191视频 | 在线一级视频 | 欧美国产免费 | 亚洲一级在线 | 国产在线a| 日韩视频国产 | 久久久久一区二区三区 | 欧美亚洲国产一区 | 黄色片网址 | 久久国产精品久久久久久 | 国产一区二区影院 | 美女视频一区二区三区 | 久草ab | 久操草| 精品国产91亚洲一区二区三区www | 精品96久久久久久中文字幕无 | 九九热视频精品在线 | 国产精品一区久久久久 | 欧美一区永久视频免费观看 | 超碰人人射| 九九热精品免费 | 久久精品网 | 日韩av高清在线 | 一区二区三区回区在观看免费视频 | 欧美一区二区人人喊爽 | 精品视频在线观看 | 亚洲高清免费 | 精品av| 九九福利 | igao视频| 日韩一区二区三区精品 | 亚洲国产午夜视频 |