mormot.core.threads--TSynThreadPool

海利鸟發表於2024-07-10

mormot.core.threads--TSynThreadPool

{ ************ 面向伺服器程序的執行緒池 }  

TSynThreadPool = class; // 前向宣告TSynThreadPool類

/// 定義了TSynThreadPool所使用的工作執行緒
TSynThreadPoolWorkThread = class(TSynThread)
protected
    fOwner: TSynThreadPool; // 執行緒池所有者
    fThreadNumber: integer; // 執行緒編號
    {$ifndef USE_WINIOCP} // 如果不使用Windows I/O完成埠
    fProcessingContext: pointer; // 正在處理的上下文,受fOwner.fSafe.Lock保護
    fEvent: TSynEvent; // 同步事件
    {$endif USE_WINIOCP}
    procedure NotifyThreadStart(Sender: TSynThread); // 通知執行緒開始
    procedure DoTask(Context: pointer); // 異常安全地呼叫fOwner.Task()
public
    /// 初始化執行緒
    constructor Create(Owner: TSynThreadPool); reintroduce;
  
    /// 終結執行緒
    destructor Destroy; override;
  
    /// 迴圈等待並執行掛起的任務,透過呼叫fOwner.Task()
    procedure Execute; override;
  
    /// 關聯的執行緒池
    property Owner: TSynThreadPool
      read fOwner;
end;

TSynThreadPoolWorkThreads = array of TSynThreadPoolWorkThread; // 執行緒池工作執行緒陣列型別

/// 一個簡單的執行緒池,用於例如快速處理HTTP/1.0請求
// - 在Windows上透過I/O完成埠實現,或在Linux/POSIX上使用經典的事件驅動方法
TSynThreadPool = class
protected
    {$ifndef USE_WINIOCP} // 如果不使用Windows I/O完成埠
    fSafe: TOSLightLock; // 使用更穩定的鎖
    {$endif USE_WINIOCP}
    fWorkThread: TSynThreadPoolWorkThreads; // 工作執行緒陣列
    fWorkThreadCount: integer; // 工作執行緒數量
    fRunningThreads: integer; // 正在執行的執行緒數量
    fExceptionsCount: integer; // 異常計數(未在程式碼中明確使用,但可能用於除錯或監控)
    fContentionAbortDelay: integer; // 由於爭用而拒絕連線的延遲時間(毫秒)
    fOnThreadTerminate: TOnNotifyThread; // 執行緒終止通知事件
    fOnThreadStart: TOnNotifyThread; // 執行緒開始通知事件
    fContentionTime: Int64; // 等待佇列可用槽位的總時間(毫秒)
    fContentionAbortCount: cardinal; // 由於爭用而中止的任務數
    fContentionCount: cardinal; // 等待佇列可用槽位的次數
    fName: RawUtf8; // 執行緒池名稱
    fTerminated: boolean; // 執行緒池是否已終止
    {$ifdef USE_WINIOCP} // 如果使用Windows I/O完成埠
    fRequestQueue: THandle; // IOCP有其自己的內部佇列
    {$else}
    fQueuePendingContext: boolean; // 當所有執行緒都忙時,是否應維護一個內部佇列
    fPendingContext: array of pointer; // 掛起的上下文陣列
    fPendingContextCount: integer; // 掛起的上下文數量
    function GetPendingContextCount: integer; // 獲取掛起上下文數量的函式
    function PopPendingContext: pointer; // 從掛起上下文陣列中彈出一個元素的函式
    function QueueLength: integer; virtual; // 獲取佇列長度的虛擬函式(可能用於除錯)
    {$endif USE_WINIOCP}
    /// 在I/O錯誤時結束執行緒
    function NeedStopOnIOError: boolean; virtual;
    /// 在通知後要執行的程序,這是一個抽象方法,需要被子類實現
    procedure Task(aCaller: TSynThreadPoolWorkThread; aContext: pointer); virtual; abstract;
    /// 中止任務的程序
    procedure TaskAbort(aContext: Pointer); virtual;

public
    /// 使用指定的執行緒數初始化執行緒池
    // - 抽象的Task()方法將由其中一個執行緒呼叫
    // - 一個執行緒池最多可以關聯256個執行緒
    // - 在Windows上,可以可選地接受一個之前使用Windows重疊I/O(IOCP)開啟的aOverlapHandle
    // - 在POSIX上,如果aQueuePendingContext=true,則將掛起的上下文儲存到內部佇列中,
    //   以便在佇列未滿時Push()返回true
    {$ifdef USE_WINIOCP}
    constructor Create(NumberOfThreads: integer = 32; aOverlapHandle: THandle = INVALID_HANDLE_VALUE; const aName: RawUtf8 = '');
    {$else}
    constructor Create(NumberOfThreads: integer = 32; aQueuePendingContext: boolean = false; const aName: RawUtf8 = '');
    {$endif USE_WINIOCP}
  
    /// 關閉執行緒池,釋放所有關聯的執行緒
    destructor Destroy; override;
  
    /// 讓執行緒池處理一個指定的任務(作為指標)
    // - 如果沒有空閒執行緒可用,並且Create(aQueuePendingContext=false)被使用,則返回false(呼叫者稍後應重試)
    // - 如果在Create中aQueuePendingContext為true,或使用了IOCP,則提供的上下文將被新增到內部列表,並在可能時處理
    // - 如果aWaitOnContention預設為false,則在佇列滿時立即返回
    // - 設定aWaitOnContention=true以等待最多ContentionAbortDelay毫秒並重試將任務排隊
    function Push(aContext: pointer; aWaitOnContention: boolean = false): boolean;
    {$ifndef USE_WINIOCP}
  
    /// 在Push()返回false後呼叫,以檢視佇列是否確實已滿
    // - 如果QueuePendingContext為false,則返回false
    function QueueIsFull: boolean;
  
    /// 如果所有執行緒都忙時,執行緒池是否應維護一個內部佇列
    // - 作為Create建構函式的引數提供
    property QueuePendingContext: boolean
      read fQueuePendingContext;
    {$endif USE_WINIOCP}
  
    /// 對此執行緒池中定義的執行緒的低階訪問
    property WorkThread: TSynThreadPoolWorkThreads
      read fWorkThread;
published
    /// 執行緒池中可用的執行緒數
    // - 對映Create()引數,即預設為32
    property WorkThreadCount: integer
      read fWorkThreadCount;
  
    /// 當前在此執行緒池中處理任務的執行緒數
    // - 範圍在0..WorkThreadCount之間
    property RunningThreads: integer
      read fRunningThreads;
  
    /// 由於執行緒池爭用而被拒絕的任務數
    // - 如果此數字很高,請考慮設定更高的執行緒數,或分析並調整Task方法
    property ContentionAbortCount: cardinal
      read fContentionAbortCount;
  
    /// 由於爭用而拒絕連線的延遲時間(毫秒)
    // - 預設為5000,即等待IOCP或aQueuePendingContext內部列表中有空間可用5秒
    // - 在此延遲期間,不接受新的連線(即不呼叫Accept),以便負載均衡器可以檢測到爭用並切換到池中的另一個例項,
    //   或直接客戶端最終可能會拒絕其連線,因此不會開始傳送資料
    property ContentionAbortDelay: integer
      read fContentionAbortDelay write fContentionAbortDelay;
  
    /// 等待佇列中可用槽位的總時間(毫秒)
    // - 爭用不會立即失敗,但會重試直到ContentionAbortDelay
    // - 此處的高數值需要對Task方法進行程式碼重構
    property ContentionTime: Int64
      read fContentionTime;
  
    /// 執行緒池等待佇列中可用槽位的次數
    // - 爭用不會立即失敗,但會重試直到ContentionAbortDelay
    // - 此處的高數值可能需要增加執行緒數
    // - 使用此屬性和ContentionTime來計算平均爭用時間
    property ContentionCount: cardinal
      read fContentionCount;
  
    {$ifndef USE_WINIOCP}
    /// 當前等待分配給執行緒的輸入任務數
    property PendingContextCount: integer
      read GetPendingContextCount;
    {$endif USE_WINIOCP}
end;

{$M-} // 關閉記憶體管理訊息

const
  // 允許TSynThreadPoolWorkThread堆疊使用最多256 * 2MB = 512MB的RAM
  THREADPOOL_MAXTHREADS = 256;

由於 TSynThreadPool類是一個高度抽象且依賴於特定實現的類(如它可能使用Windows的I/O完成埠或Linux/POSIX的事件驅動機制),編寫一個完整的例程程式碼可能會相當複雜,並且需要模擬或實際實現這些依賴項。然而,我可以提供一個簡化的示例,該示例展示瞭如何建立 TSynThreadPool例項、如何向其推送任務,並如何大致實現 Task方法。

請注意,以下程式碼是一個高度簡化的示例,並不包含所有 TSynThreadPool類定義中的功能,特別是與Windows I/O完成埠或Linux/POSIX事件驅動機制相關的部分。此外,由於 TSynThreadPool是一個假設的類(因為它不是Delphi標準庫或廣泛認可的第三方庫的一部分),我將基於您提供的類定義來編寫這個示例。

program TSynThreadPoolDemo;

{$MODE DELPHI}

uses
  SysUtils, Classes; // 引入必要的單元

// 假設TSynThreadPool及其依賴項已經在某個單元中定義
// 這裡我們使用一個佔位符單元名YourThreadPoolUnit
uses YourThreadPoolUnit;

// 一個簡單的任務上下文類(僅作為示例)
type
  TMyTaskContext = record
    Data: Integer;
  end;

// TSynThreadPool的Task方法的實現類
type
  TMyThreadPool = class(TSynThreadPool)
  protected
    procedure Task(aCaller: TSynThreadPoolWorkThread; aContext: Pointer); override;
  end;

{ TMyThreadPool }

procedure TMyThreadPool.Task(aCaller: TSynThreadPoolWorkThread; aContext: Pointer);
var
  Ctx: PMyTaskContext;
begin
  Ctx := PMyTaskContext(aContext);
  WriteLn('Processing task with data: ', Ctx.Data);
  // 在這裡新增處理任務的程式碼
  // ...
end;

var
  Pool: TMyThreadPool;
  Ctx: TMyTaskContext;
  I: Integer;

begin
  try
    // 建立一個執行緒池例項,假設我們想要使用4個工作執行緒
    Pool := TMyThreadPool.Create(4);
    try
      // 模擬向執行緒池推送一些任務
      for I := 1 to 10 do
      begin
        Ctx.Data := I;
        if not Pool.Push(@Ctx) then
        begin
          // 在這個簡化的示例中,我們不會處理Push返回false的情況
          // 在實際應用中,您可能需要等待、重試或將任務放入另一個佇列中
          WriteLn('Failed to push task to the pool (this should not happen in this simplified example)');
        end;
      end;

      // 在這個簡化的示例中,我們沒有等待所有任務完成
      // 在實際應用中,您可能需要等待執行緒池中的所有任務都完成後再繼續
      // ...

    finally
      // 銷燬執行緒池例項,這將釋放所有關聯的資源
      Pool.Free;
    end;
  except
    on E: Exception do
      WriteLn('An error occurred: ', E.Message);
  end;
  // 保持控制檯視窗開啟,直到使用者按任意鍵
  WriteLn('Press Enter to exit...');
  ReadLn;
end.

// 注意:由於TSynThreadPool是一個假設的類,並且上述程式碼沒有實現所有細節(如執行緒池的實際工作執行緒管理、任務佇列等),
// 因此這個示例主要是為了展示如何使用該類(如果它存在的話)的大致結構。
// 在實際應用中,您需要根據TSynThreadPool類的具體實現來調整此程式碼。

重要說明

  1. 佔位符單元:在上述程式碼中,我使用了 YourThreadPoolUnit作為包含 TSynThreadPool類定義的佔位符單元名。在實際應用中,您需要將其替換為包含該類定義的實際單元名。
  2. 任務上下文:我定義了一個簡單的 TMyTaskContext記錄型別來作為任務的上下文。在實際應用中,您可能需要根據需要定義更復雜的上下文型別。
  3. 錯誤處理:在 Push方法返回 false的情況下,上述程式碼僅列印了一條訊息,並沒有採取任何恢復措施。在實際應用中,您可能需要實現更復雜的錯誤處理邏輯(如重試、等待或將任務放入另一個佇列中)。
  4. 等待任務完成:上述程式碼沒有等待執行緒池中的所有任務都完成。在實際應用中,您可能需要實現某種形式的等待機制(例如,使用同步事件或計數器)來確保所有任務都已完成後再繼續執行後續程式碼。
  5. 執行緒池實現:由於 TSynThreadPool是一個假設的類,並且其實現細節(如工作執行緒的管理、任務佇列的實現等)並未在您的類定義中給出,因此上述程式碼僅提供了一個大致的框架。在實際應用中,您需要根據 TSynThreadPool類的具體實現來調整此程式碼。