首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

Delphi 單例模式實作:線程安全的定時任務防重入保護

     編輯:Delphi

定時器觸發任務時,若上一輪任務尚未完成便再次觸發,會產生多執行緒競爭、資料錯亂。單例模式配合 FreeOnTerminate 與全域實例變數,可確保同一時間只有一個工作執行緒存在。本文展示 Windows 服務定時下發簡訊場景的完整實作。

核心場景

  • Timer 每分鐘檢查是否到達「下發時間」(存於 INI)。
  • 到達時建立 TSendThread 執行耗時操作 (查詢資料庫、發送簡訊)。
  • 若發送超過 1 分鐘,下一輪 Timer 再次觸發,必須不建立新執行緒,等待現有執行緒結束。

單例執行緒類別


unit Unit_Send;
interface
uses SysUtils, Classes, StrUtils, Main;

type
  TSendThread = class(TThread)
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    class function GetInstance: TSendThread;
  protected
    procedure Execute; override;
  end;

var
  Global_Instance: TSendThread; // 全域唯一實例

implementation
uses DB;

class function TSendThread.GetInstance: TSendThread;
begin
  if Global_Instance = nil then
    Global_Instance := TSendThread.Create(False); // 立即啟動
  Result := Global_Instance;
end;

constructor TSendThread.Create(CreateSuspended: Boolean);
begin
  if Global_Instance = nil then begin
    inherited Create(CreateSuspended);
    FreeOnTerminate := True; // 執行緒結束自動 Free
  end else
    raise Exception.Create('Cannot create more than one TSendThread instance!');
end;

destructor TSendThread.Destroy;
begin
  inherited;
end;

procedure TSendThread.Execute;
var
  User: TUserInfo;
  SQL: String;
begin
  // 1. 執行核心任務
  SQL := 'SELECT * FROM ' + Main.MainUserTable + ' WHERE destroy=0 ORDER BY id';
  Service1.ADOQuery_Send.Connection := Conn_Server;
  SQLQuery(Service1.ADOQuery_Send, SQL);

  while (not Service1.ADOQuery_Send.Eof) and (not Terminated) do begin
    User.SeqID := '0';
    User.UID := '';
    User.Spc := GetMsg('PARAMETER', 'Spcode', TheExeName + '.ini');
    User.RecordID := '0';
    User.Mob := Service1.ADOQuery_Send.FieldByName('mobile').AsString;
    AutoJoke(User); // 發送簡訊
    Service1.ADOQuery_Send.Next;
  end;

  // 2. 任務完成後睡眠 60 秒,防止 Timer 立即再次觸發
  Sleep(600001); // 10 分鐘 + 1 秒

  // 3. 清除全域實例,允許下一輪建立
  Global_Instance := nil;
  Terminate;
end;
end.

Timer 觸發邏輯


procedure TService1.Timer_MainTimer(Sender: TObject);
var NowTime, TargetTime: String;
begin
  try
    DateTimeToString(NowTime, 'hh:nn', Now);
    TargetTime := LeftStr(GetMsg('GAME', 'SendTime', TheExeName + '.ini'), 4); // HH:NN
    if LeftStr(NowTime, 4) = TargetTime then begin
      Global_Instance := TSendThread.GetInstance; // 獲取或建立單例
    end;
  except
    on E: Exception do LogError('Timer建立執行緒失敗: ' + E.Message);
  end;
end;

關鍵設計要點

機制作用
Global_Instance 變數進程級單一實例標誌
GetInstance 類別方法雙重檢查鎖定 (單執行緒環境下足夠)
Constructor 檢查 Global_Instance防止外部直接 Create 繞過單例
FreeOnTerminate = True執行緒結束自動釋放物件記憶體
Execute 結束前 Sleep + 清 nil拉開時間窗,避免 Timer 密集觸發
Terminated 檢查支援外部優雅取消

進階:多執行緒環境下的線程安全單例

若 GetInstance 可能被多執行緒同時呼叫,需用 Critical Section 或 InterlockedCompareExchange:


class function TSendThread.GetInstance: TSendThread;
var Temp: TSendThread;
begin
  if Global_Instance = nil then begin
    Temp := TSendThread.Create(True); // 暫停建立
    if InterlockedCompareExchange(Pointer(Global_Instance), Temp, nil) <> nil then
      Temp.Free; // 另一執行緒搶先建立,丟棄新建實例
  end;
  Result := Global_Instance;
end;

替代方案比較

方案優點缺點
單例執行緒 (本文)邏輯封裝完整、自動清理、支援優雅退出需手動管理全域變數
TEvent + 單一工作執行緒標準生產者消費者模式、可排隊多任務架構較複雜
Timer 單次觸發 + 任務完成重啟無全域狀態、簡單任務超時會導致間隔變長、難以精確定時
資料庫鎖 / 檔案鎖跨進程互斥效能低、殘留鎖風險

單例執行緒模式最適合「定時觸發、單任務、執行時間不可預測、嚴禁並發」的業務場景,代碼清晰、維護容易。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved