程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> Delphi設計簡易對象垃圾回收框架

Delphi設計簡易對象垃圾回收框架

編輯:Delphi

1 緣起

1.1 我的一個出錯程序

程序名稱:呼叫處理模塊的壓力測試工具,分為客戶端和服務端。

開發工具:Delhpi 5

相關技術:客戶端通過與服務端建立Socket連接來模擬一組電話機的撥入、按鍵、等待、掛機等過程。服務端對Socket事件以及收到的數據包進行預處理,並轉化為抽象的呼叫模型數據,然後發送給更上層的呼叫處理模塊。由於呼叫處理模塊是硬件無關的(與語音板卡、交換機類型均無關),因此通過此壓力測試工具可以比較真實地模擬海量呼叫,以達到測試呼叫處理模塊程序的邏輯正確性及其性能的目的。

由於系統設計時的某些考慮,該測試工具被分作客戶端和服務端兩個程序來實現,且采用socket進行通訊。現在想來,其實不如整合成一個程序實現更為簡單——但也正因為采用兩個程序來實現,才引發了後面的一些問題,並由此引入了簡單的垃圾回收框架。

1.2 問題

在測試工具的使用過程中,我們發現當呼叫量巨大,且測試工具動作頻繁的情況下,系統出現以下錯誤:

訪問地址錯(EAccessViolation),代碼地址位於$0046FC80附近,訪問地址多為$00000028。

出現EinvalidCast錯誤,該錯誤表明對一個地址進行類類型轉換時出錯(采用as關鍵字)。

程序內多處斷言失敗,出現許多引用已銷毀對象的情況。

仔細檢查程序後,我仍然認為這一切簡直是不可思議!而且,本來用於對別的程序進行測試的程序自身卻出現這類問題,幾乎讓我無地自容!

為了挽回自己的聲譽,我不得不成沉住氣來仔細跟蹤錯誤,排解問題!

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 概述

簡易的垃圾回收非常簡單:

使用TThreadList支持線程並發訪問,並保存待回收的對象指針;

提供Put方法保存待回收對象;

提供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進行對象的銷毀和回收。

銷毀

AObject := TObject.Create;
GarbagCollector.Put(AObject);

回收

可以在定時器、線程以及其他場合調用Recycle方法。

MaxCount是用於控制每次銷毀個數的參數,主要是怕一次性銷毀太多占用過多的cpu。

(突然發現還可以擴展為限制時間進行銷毀,比如每次銷毀耗時不超過的n毫秒)。

3.4 使用場合

在本案例中,為了防止對象過早銷毀引起訪問沖突,而引入了垃圾回收技術。

在其它場合,比如為了提高某些程序的主觀性能,也可以引入該技術。比如完成某些特定任務的程序,在處理過程中會產生臨時的對象,而銷毀這些對象又比較耗時。因此,為了盡早地結束任務,可以把這些臨時對象保存至垃圾池中。待作業(任務)完成,並且等一段時間後cpu比較空閒時,再把臨時對象真正銷毀。此做法的真谛就是以空間換取時間——與某些系統預創建對象,並重復利用對象以提高性能的做法相同。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved