回覆列表
  • 1 # 使用者3177994670834

    Delphi同步互斥總結

    多個執行緒同時訪問一個共享資源或資料時,需要考慮執行緒同步,Synchronize()是在一個隱蔽的窗口裡執行,如果在這裡你的任務很繁忙,你的主視窗會阻塞掉;Synchronize()只是將該執行緒的程式碼放到主執行緒中執行,並非執行緒同步。

    臨 界區是一個程序裡的所有執行緒同步的最好辦法,他不是系統級的,只是程序級的,也就是說他可能利用程序內的一些標誌來保證該程序內的執行緒同步,據

    Richter說是一個記數迴圈;臨界區只能在同一程序內使用;臨界區只能無限期等待,不過2k增加了TryEnterCriticalSection函

    數實現0時間等待。 互斥則是保證多程序間的執行緒同步,他是利用系統核心物件來保證同步的。由於系統核心物件可以是有名字的,因此多個

    程序間可以利用這個有名字的核心物件保證系統資源的執行緒安全性。互斥量是Win32

    核心物件,由作業系統負責管理;互斥量可以使用WaitForSingleObject實現無限等待,0時間等待和任意時間等待。常見的執行緒同步方法如下:

    1. 臨界區

    臨界區是一種最直接的執行緒同步方式。所謂臨界區,就是一次只能由一個執行緒來執行的一段程式碼。如果把初始化陣列的程式碼放在臨界區內,另一個執行緒在第一個執行緒處理完之前是不會被執行的。使用方法如下:

    //在窗體建立中

    InitializeCriticalSection(Critical1)

    //在窗體銷燬中

    DeleteCriticalSection(Critical1)

    //線上程中

    EnterCriticalSection(Critical1)

    ……保護的程式碼

    LeaveCriticalSection(Critical1)

    2. 互斥

    互斥非常類似於臨界區,除了兩個關鍵的區別:首先,互斥可用於跨程序的執行緒同步。其次,互斥能被賦予一個字串名字,並且透過引用此名字建立現有互斥物件的附加控制代碼。

    臨界區與事件物件(比如互斥物件)的最大的區別是在效能上。臨界區在沒有執行緒衝突時,要用10 ~

    15個時間片,而事件物件由於涉及到系統核心要用400~600個時間片。

    Mutex(互斥物件),是用於序列化訪問資源的全域性物件。我們首先設定互斥物件,然後訪問資源,最後釋放互斥物件。在設定互斥物件時,如果另一個執行緒(或程序)試圖設定相同的互斥物件,該執行緒將會停下來,直到前一個執行緒(或程序)釋放該互斥物件為止。注意它可以由不同應用程式共享。使用方法如下:

    //在窗體建立中

    hMutex:=CreateMutex(nil,false,nil)

    //在窗體銷燬中

    CloseHandle(hMutex)

    //線上程中

    WaitForSingleObject(hMutex,INFINITE)

    ……保護的程式碼

    ReleaseMutex(hMutex)

    3. 訊號量

    另一種使執行緒同步的技術是使用訊號量物件。它是在互斥的基礎上建立的,但訊號量增加了資源計數的功能,預定數目的執行緒允許同時進入要同步的程式碼。可以用CreateSemaphore()來建立一個訊號量物件,

    因為只允許一個執行緒進入要同步的程式碼,所以訊號量的最大計數值(lMaximumCount)要設為1。其實Mutex就是最大計數為一的Semaphore。使用方法如下:

    //在窗體建立中

    hSemaphore:= CreateSemaphore(nil,lInitialCount,lMaximumCount,lpName)

    //在窗體銷燬中

    CloseHandle(hSemaphore)

    //線上程中

    WaitForSingleObject(hSemaphore,INFINITE)

    ……保護的程式碼

    ReleaseSemaphore(hSemaphore, lReleaseCount, lpPreviousCount)

    4.WaitForSingleObject函式的返值:

    WAIT_ABANDONED指定的物件是互斥物件,並且擁有這個互斥物件的執行緒在沒有釋放此物件之前就已終止。此時就稱互斥物件被拋棄。這種情況下,這個互斥物件歸當前執行緒所有,並把它設為非發訊號狀態;

    WAIT_OBJECT_0 指定的物件處於發訊號狀態;

    WAIT_TIMEOUT等待的時間已過,物件仍然是非發訊號狀態;

    Delphi 常用的臨界區物件TCriticalSection(Delphi) 、TRtlCriticalSection

    TRtlCriticalSection 是一個結構體,在windows單元中定義;

    是InitializeCriticalSection,EnterCriticalSection,LeaveCriticalSection,

    DeleteCriticalSection 等這幾個kernel32.dll中的臨界區操作API的引數;

    TCriticalSection是在SyncObjs單元中實現的類,它對上面的那些臨界區操作API函式進行了了封裝,簡化並方便了在Delphi的使用;如TCriticalSection.Create,TCriticalSection.Enter,

    TcriticalSection.Leave等;透過呼叫上面響應的API函式實現。

    執行緒同步的多種辦法中,使用臨界區最簡單,也是效率最高的辦法(CPU佔用時間最少)

    使用臨界區程式碼如下:

    先宣告一個TRTLCriticalSection型別的全域性變數

    var

    MyCs:TRTLCriticalSection;

    在程式開始或建立執行緒之前,初始化

    InitializeCriticalSection(MyCs);//初始化臨界區

    再線上程中要同步的地方加入

    EnterCriticalSection(MyCs); //進入臨界區

    try

    //程式程式碼

    finally

    LeaveCriticalSection(MyCs); //離開臨界區

    end;

    補充今天遇到的關於Application.ProcessMessages同步的問題:有一個函式Fn按執行順序可分為A->B->C

    3大塊,其中B塊有要繪製各種視窗介面的操作很複雜且耗時較長,並且裡面用到了Application.ProcessMessages,程式執行測試時發現如果在Fn執行B繪製視窗的過程沒結束時又呼叫Fn函式去繪製其它視窗就可能會導致程式崩潰,一開始嘗試用TcriticalSection變數解決,完全沒用,最後用增加一個全域性變數的方法解決:定義一個全域性Boolean型變數flag,設定初始值為True,改造Fn函式的邏輯為A->

    if flag then

    Begin

    Flag:=False;

    B;

    Flag:=True;

    End;

    ->C

    問題成功解決。

    順便總結Application.ProcessMessages的作用:執行一個非常耗時的迴圈,那麼在這個迴圈結束前,程式可能不會響應任何事件,按鈕沒有反應,程式設定無法繪製窗體,看上去就如同死了一樣,這有時不是很方便,例如於終止迴圈的機會都沒有了,又不想使用多執行緒時,這時你就可以在迴圈中加上這麼一句,每次程式執行到這句時,程式就會讓系統響應一下訊息,從而使你有機會按按鈕,窗體有機會繪製。所起作用類似於VB中DoEvent方法.

    呼叫ProcessMessages來使應用程式處於訊息佇列能夠進行訊息處理,ProcessMessages將Windows訊息進行迴圈輪轉,直至訊息為空,然後將控制返回給應用程式。

    注示:僅在應用程式呼叫ProcessMessages時勿略訊息程序效果,而並非在其他應用程式中。在冗長的操作中,呼叫ProcessMessages週期性使得應用程式對畫筆或其他資訊產生迴應。

    ProcessMessages不充許應該程式空閒,而HandleMessage則然.使用ProcessMessages一定要保證相關程式碼是可重入的,如果實在不行也可按我上面的方法實現同步。

  • 中秋節和大豐收的關聯?
  • 小時候過年你被鞭炮炸過嗎?