delphi 垃圾回收框架
1?????? 緣起
1.1?? 我的一個出錯程序
程序名稱:呼叫處理模塊的壓力測試工具,分為客戶端和服務端。
開發工具:Delhpi 5
相關技術:客戶端通過與服務端建立Socket連接來模擬一組電話機的撥入、按鍵、等待、掛機等過程。服務端對Socket事件以及收到的數據包進行預處理,并轉化為抽象的呼叫模型數據,然后發送給更上層的呼叫處理模塊。由于呼叫處理模塊是硬件無關的(與語音板卡、交換機類型均無關),因此通過此壓力測試工具可以比較真實地模擬海量呼叫,以達到測試呼叫處理模塊程序的邏輯正確性及其性能的目的。
由于系統設計時的某些考慮,該測試工具被分作客戶端和服務端兩個程序來實現,且采用socket進行通訊?,F在想來,其實不如整合成一個程序實現更為簡單——但也正因為采用兩個程序來實現,才引發了后面的一些問題,并由此引入了簡單的垃圾回收框架。
1.2?? 問題
在測試工具的使用過程中,我們發現當呼叫量巨大,且測試工具動作頻繁的情況下,系統出現以下錯誤:
n???????? 訪問地址錯(EAccessViolation),代碼地址位于$0046FC80附近,訪問地址多為$00000028。
n???????? 出現EinvalidCast錯誤,該錯誤表明對一個地址進行類類型轉換時出錯(采用as關鍵字)。
n???????? 程序內多處斷言失敗,出現許多引用已銷毀對象的情況。
仔細檢查程序后,我仍然認為這一切簡直是不可思議!而且,本來用于對別的程序進行測試的程序自身卻出現這類問題,幾乎讓我無地自容!
為了挽回自己的聲譽,我不得不成沉住氣來仔細跟蹤錯誤,排解問題!
2?????? 解決辦法
2.1?? 查錯
其實問題的解決還比較順利。
通過查看程序的調用棧,發現程序出錯前總是停留在發送Socket數據包的過程里。接著,進一步通過單步跟蹤,發現在發送數據包的過程中,Socket檢測到對端連接已經斷開,就會觸發OnDisconnect事件。而我正是在ServerSocket的OnDisconnect事件中根據傳遞進來的Socket句柄,找到對應的對象將之銷毀的。
我在ServerSocket的OnDisconnect事件中的代碼如下:
procedure Txxxx.ServerClientDisconnect(Sender: TObject;
? Socket: TCustomWinSocket);
Begin
? …
? FLines.DestroyLineBySocket(Socket);//正是這一句,在不合適的時機釋放了對象
? …
End;
問題是這么出現的。
比如,在某個過程中具有如下代碼(前面為行號):
1??? FLine.DoSomething;
2??? FLine.SendSocketData;
3??? FLine.DoOtherThings;
其中,FLine是代表一路呼叫的對象。該對象內部引用了一個TCustomWinSocket指針。SendSocketData就是利用此Socket進行數據發送。
Flines是TLine對象的容器類的一個實例。
由此不難解讀前述的各類錯誤:
1.? 由于行2的Socket連接斷開導致FLine對象釋放,因此行3訪問DoOtherThings幾乎必然造成訪問地址錯;
2.? 由于行2的對象銷毀,因此程序中類似“Object as TLine”的代碼導致第二類錯誤;
3.? 由于對象提前銷毀,善后處理工作未到位導致第三類錯誤;
2.2?? 解決方案
明白其原因后,問題解決起來就容易多了。
上述問題不外乎兩個方案:
一,????????????? 判斷實例是否存在
在DoOtherThings之后,判斷FLine對象是否仍然處于Flines之中,若是則繼續處理,否則結束處理;
二,????????????? 延遲銷毀FLine對象
在ServerSocket的OnDisconnect中,將FLine對象拋入垃圾池,待時機成熟時再銷毀。
考慮到方案一所要改動的代碼量較大,同時,此種方案代碼也不甚優美,因此決定采用方案二,即引入垃圾回收機制來解決問題。方案二的要點是選擇合適的時機真正銷毀對象。而對于這一點,問題倒不大,只需選擇消息循環中處理消息的第一個環節進行回收即可。因為在之后的處理環節中,必然能夠確保對FLine是否仍然有效的檢查。
3?????? 簡易對象垃圾回收框架(untGarbagCollector)
3.1?? 概述
簡易的垃圾回收非常簡單:
n???????? 使用TThreadList支持線程并發訪問,并保存待回收的對象指針;
n???????? 提供Put方法保存待回收對象;
n???????? 提供Recycle方法進行真正的回收(因為所有對象均自TObject派生而來)。
3.2?? 實現代碼
unit untGarbagCollector;
interface
uses
? Classes;
type
? TGarbagCollector = Class(TObject)
? private
??? FList: TThreadList;
? public
??? constructor Create;
??? destructor Destroy; override;
??? procedure Put(const AObject: TObject);
??? procedure Recycle(const MaxCount: Integer);
? end;
function GarbagCollector: TGarbagCollector;
implementation
var
? _GarbagCollector: TGarbagCollector;
function GarbagCollector: TGarbagCollector;
begin
? if not Assigned(_GarbagCollector) then
??? _GarbagCollector := TGarbagCollector.Create;
? result := _GarbagCollector;
end;
{ TGarbagCollect }
constructor TGarbagCollector.Create;
begin
? FList := TThreadList.Create;
end;
destructor TGarbagCollector.Destroy;
begin
? try
??? Recycle(FList.LockList.Count);
? finally
??? FList.UnlockList;
? end;
? FList.Free;
end;
procedure TGarbagCollector.Put(const AObject: TObject);
begin
? try
??? FList.LockList.Add(AObject);
? finally
??? FList.UnlockList;
? end;
end;
procedure TGarbagCollector.Recycle(const MaxCount: Integer);
var
? I: Integer;
? AList: TList;
begin
? AList := FList.LockList;
? try
??? I := 0;
??? while (AList.Count > 0) and (I < MaxCount) do
??? begin
????? TObject(AList.Last).Free;
????? AList.Delete(AList.Count - 1);
????? Inc(I);
??? end;
? finally
??? FList.UnlockList;
? end;
end;
initialization
finalization
? if Assigned(_GarbagCollector) then
??? _GarbagCollector.Free;
end.
3.3?? 使用舉例
引用untGarbagCollector單元后,可以直接使用GarbagCollector進行對象的銷毀和回收。
n???????? 銷毀
AObject := TObject.Create;
GarbagCollector.Put(AObject);
n???????? 回收
可以在定時器、線程以及其他場合調用Recycle方法。
MaxCount是用于控制每次銷毀個數的參數,主要是怕一次性銷毀太多占用過多的cpu。
(突然發現還可以擴展為限制時間進行銷毀,比如每次銷毀耗時不超過的n毫秒)。
3.4?? 使用場合
在本案例中,為了防止對象過早銷毀引起訪問沖突,而引入了垃圾回收技術。
在其它場合,比如為了提高某些程序的主觀性能,也可以引入該技術。比如完成某些特定任務的程序,在處理過程中會產生臨時的對象,而銷毀這些對象又比較耗時。因此,為了盡早地結束任務,可以把這些臨時對象保存至垃圾池中。待作業(任務)完成,并且等一段時間后cpu比較空閑時,再把臨時對象真正銷毀。此做法的真諦就是以空間換取時間——與某些系統預創建對象,并重復利用對象以提高性能的做法相同。
總結
以上是生活随笔為你收集整理的delphi 垃圾回收框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: delphi中採用bpl共享模塊的幾點釋
- 下一篇: c#下简单的文件读写