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);//初始化臨界區
在程序結束或所有線程結束後,刪除它
DeleteCriticalSection(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壹定要保證相關代碼是可重入的,如果實在不行也可按我上面的方法實現同步。