返回文章列表

Delphi 平行程式設計 TTaskFuture 與管線應用

本文探討 Delphi 平行程式設計,特別是 TTask.Future 和管線模式的應用。文章首先介紹如何使用 TTask.Future 執行非同步運算,包含建立 Future、取得結果和例外處理。接著,文章詳細說明 TParallel.For

Delphi 平行運算

Delphi 的平行程式函式庫提供 TTask.Future 簡化非同步操作,適用於耗時任務如複雜計算或 I/O 操作。建立 Future 只需呼叫 TTask.Future 並傳入對應函式。透過 Value 屬性取得結果,但需注意阻塞問題。使用匿名方法則可在運算後於主執行緒執行回呼。TParallel.For 將迴圈平行化,提升效率,但需注意資料分享問題,使用 TInterlocked 系列函式可避免此問題。將 TParallel.For 包裝在 FutureAsync/Await 中可避免阻塞。管線模式則將任務分解成多階段平行處理,各階段間透過佇列傳遞資料,提升效率。網路蜘蛛的實作可有效展現管線模式的優勢,其包含過濾、下載和解析三個階段,各階段負責不同任務,最後將結果整合。

深入探索平行程式設計實踐

使用 TTask.Future 進行非同步運算

在 Delphi 的平行程式函式庫(Parallel Programming Library, PPL)中,TTask.Future 提供了一種簡便的方式來執行非同步運算並在稍後取得結果。這種機制特別適合於那些需要較長時間執行的任務,例如複雜的計算或 I/O 操作。

建立 Future

建立一個 Future 非常簡單,只需呼叫 TTask.Future<integer> 並傳入一個傳回整數的函式或匿名方法即可。例如,下面的程式碼展示瞭如何使用 TTask.Future 來執行一個名為 CountPrimes 的函式,該函式計算某個範圍內的質數數量:

procedure TfrmFuture.btnFutureClick(Sender: TObject);
begin
  FFuture := TTask.Future<integer>(CountPrimes);
end;

取得 Future 的結果

一旦 Future 被建立,你可以透過存取其 Value 屬性來取得運算結果。如果運算尚未完成,存取 Value 將會阻塞直到結果可用。下面的程式碼展示瞭如何取得 Future 的結果並將其顯示在列表框中:

procedure TfrmFuture.btnGetValueClick(Sender: TObject);
begin
  ListBox1.Items.Add('Result = ' + FFuture.Value.ToString);
  FFuture := nil;
end;

使用匿名方法改進 Future

另一種使用 Future 的方式是傳入一個匿名方法,這樣可以在運算完成後於主執行緒中執行特定的回呼方法。以下是一個範例:

procedure TfrmFuture.btnFuture2Click(Sender: TObject);
begin
  FFuture := TTask.Future<integer>(
    function: Integer
    begin
      Result := CountPrimes;
      TThread.Queue(nil, ReportFuture);
    end);
end;

procedure TfrmFuture.ReportFuture;
begin
  ListBox1.Items.Add('Result = ' + FFuture.Value.ToString);
  FFuture := nil;
end;

處理 Future 中的例外

如果在 Future 的運算過程中丟擲例外,該例外將被 PPL 捕捉並在主執行緒存取 Value 屬性時重新丟擲。因此,建議在背景任務中自行處理例外。

平行 For 迴圈

PPL 提供了一個多執行緒版本的 for 迴圈,即 TParallel.For。它允許簡單地將迴圈平行化,但使用時須小心避免資料分享問題。

基本用法

要將一個標準的 for 迴圈轉換為平行 for,只需呼叫 TParallel.For 並傳入迴圈範圍和工作方法即可。例如:

procedure TbtnParallelFor.btnParalleForBadClick(Sender: TObject);
var
  count: Integer;
  sw: TStopwatch;
begin
  sw := TStopwatch.StartNew;
  count := 0;
  TParallel.For(2, CHighestNumber,
    procedure (i: integer)
    begin
      if IsPrime(i) then
        Inc(count); // 這裡存在資料分享問題
    end);
  sw.Stop;
  // ...
end;

修復資料分享問題

上述範例中,多個執行緒同時更新 count 變數,導致資料競爭。為瞭解決這個問題,可以使用 TInterlocked.Increment 取代 Inc

TParallel.For(2, CHighestNumber,
  procedure (i: integer)
  begin
    if IsPrime(i) then
      TInterlocked.Increment(count);
  end);

使用 Future 包裝 Parallel For

由於 TParallel.For 會阻塞呼叫執行緒直到運算完成,因此可以將其包裝在 FutureAsync/Await 中以實作非阻塞執行。例如:

procedure TbtnParallelFor.btnAsyncParallelForClick(Sender: TObject);
begin
  // 使用 Future 包裝 ParallelCountPrime 函式
end;

平行處理實務探討

在現代軟體開發中,平行處理技術已成為提升應用程式效能的重要手段。Delphi 的平行程式函式庫(Parallel Programming Library)提供了一系列強大的工具,使開發者能夠輕鬆實作平行處理。本文將探討平行處理中的幾個重要概念,包括 TParallel.ForFuture 模式以及管道(Pipeline)模式。

使用 TParallel.For 進行平行迴圈

TParallel.For 是 Delphi 平行程式函式庫中的一個重要功能,它允許開發者將傳統的迴圈轉換為平行執行的任務。以下是一個簡單的範例,展示如何使用 TParallel.For 來計算指定範圍內的質數數量:

function TbtnParallelFor.ParallelCountPrimes: Integer;
var
  count: Integer;
begin
  count := 0;
  TParallel.For(2, CHighestNumber,
    procedure(i: Integer)
    begin
      if IsPrime(i) then
        TInterlocked.Increment(count);
    end);
  Result := count;
  TThread.Queue(nil,
    procedure
    begin
      FStopwatch.Stop;
      ListBox1.Items.Add('非同步平行迴圈: ' + FParallelFuture.Value.ToString + ' 個質數。總耗時: ' + FStopwatch.ElapsedMilliseconds.ToString);
      FParallelFuture := nil;
    end);
end;

內容解密:

  1. TParallel.For:用於將迴圈平行化,自動分配多個執行緒來處理迴圈中的任務。
  2. TInterlocked.Increment:用於執行緒安全地增加計數器 count 的值,避免多執行緒同時存取造成的資料競爭。
  3. TThread.Queue:將匿名函式加入主執行緒的佇列中執行,確保 UI 更新在主執行緒中進行。

Future 模式與非同步處理

Future 模式允許開發者在背景執行緒中執行任務,並在任務完成後取得結果。以下範例展示瞭如何使用 TTask.Future 來非同步執行 ParallelCountPrimes 函式:

begin
  FStopWatch := TStopwatch.StartNew;
  FParallelFuture := TTask.Future<Integer>(ParallelCountPrimes);
end;

內容解密:

  1. TTask.Future:建立一個非同步任務,並傳回一個 IFuture<T> 介面,用於取得任務的結果。
  2. ParallelCountPrimes:在背景執行緒中執行的函式,負責計算質數數量。

例外處理

在使用 TParallel.For 時,若迴圈中的任務丟擲例外,這些例外將被捕捉並重新拋出在主執行緒中。開發者可以使用 try..except 區塊來處理這些例外。以下範例展示瞭如何捕捉並記錄 TParallel.For 中的例外:

procedure TbtnParallelFor.btnParallelForExceptionClick(Sender: TObject);
var
  i: Integer;
begin
  ListBox1.Items.Add('
---
');
  try
    TParallel.For(1, 10,
      procedure(i: Integer)
      begin
        Sleep(100);
        raise Exception.Create('執行緒 ' + TThread.Current.ThreadID.ToString + ' 中的例外');
      end);
  except
    on E: EAggregateException do
      for i := 0 to E.Count - 1 do
        if not Assigned(E[i]) then
          ListBox1.Items.Add(i.ToString + ': nil')
        else
          ListBox1.Items.Add(i.ToString + ': ' + E[i].ClassName + ': ' + E[i].Message);
    on E: Exception do
      ListBox1.Items.Add(E.ClassName + ': ' + E.Message);
  end;
end;

內容解密:

  1. EAggregateException:當 TParallel.For 中的多個任務丟擲例外時,這些例外將被收集在 EAggregateException 中。
  2. 例外處理:透過迴圈遍歷 EAggregateException 中的例外,並記錄每個例外的詳細資訊。

管道(Pipeline)模式

管道模式是一種將任務分解為多個階段,並在不同執行緒中平行執行的技術。每個階段處理資料的一部分,並將結果傳遞給下一個階段。以下是一個簡單的管道模式範例,用於實作一個簡單的網頁爬蟲:

// 簡化的管道模式範例,用於網頁爬蟲
procedure TPipelineDemo.Start;
begin
  // 初始化管道階段
  Stage1 := TStage.Create(ProcessURL);
  Stage2 := TStage.Create(ProcessHTML);
  
  // 連線階段
  Stage1.NextStage := Stage2;
  
  // 啟動管道
  Stage1.Start;
end;

procedure TPipelineDemo.ProcessURL(const Input: string);
begin
  // 處理 URL
  var html := DownloadHTML(Input);
  Stage1.Send(html);
end;

procedure TPipelineDemo.ProcessHTML(const Input: string);
begin
  // 處理 HTML 內容
  var links := ExtractLinks(Input);
  // 將結果傳遞給下一個階段或輸出
end;

圖表翻譯:

此圖示展示了一個簡單的管道流程,其中資料從一個階段傳遞到下一個階段,每個階段在不同的執行緒中平行執行。

管道模式流程圖

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 圖表翻譯:

rectangle "HTML" as node1
rectangle "連結" as node2

node1 --> node2

@enduml

網路蜘蛛管線實作探討

網路蜘蛛(Web Spider)是一種自動化程式,能夠遍歷網頁並提取所需資訊。本文將探討如何使用管線(Pipeline)架構來實作網路蜘蛛,並介紹其內部實作機制。

網路蜘蛛管線介面

網路蜘蛛管線的介面非常簡單,主要包含啟動、停止及事件處理等功能。以下為簡化的介面定義:

type
  TWebSpider = class
  public
    procedure Start(const baseUrl: string);
    procedure Stop;
    property OnPageProcessed: TProc<string> read FOnPageProcessed write FOnPageProcessed;
    property OnFinished: TProc read FOnFinished write FOnFinished;
  end;

使用者可以透過 Start 方法啟動網路蜘蛛,並傳入初始 URL。OnPageProcessed 事件會在每個網頁處理完成後觸發,而 OnFinished 事件則在整個處理過程完成後觸發。

網路蜘蛛管線實作

網路蜘蛛管線內部包含三個主要階段:

  1. 過濾階段(Filter Stage):接收 URL 並檢查是否已經處理過,若未處理則傳遞給下載階段。
  2. 下載階段(Downloader Stage):接收 URL 並下載其內容,若成功則將 URL 和 HTML 傳遞給解析階段。
  3. 解析階段(Parser Stage):解析 HTML 並提取相關 URL,將其傳回過濾階段進行處理。

管線設定

管線的設定是在 Start 方法中完成的,程式碼如下:

procedure TWebSpider.Start(const baseUrl: string);
var
  i: integer;
begin
  FPipeline := TPipeline<string, string>.Create(
    10000, 100,
    procedure
    var
      url: string;
    begin
      if assigned(OnPageProcessed) then
        while FPipeline.Output.Read(url) do
          OnPageProcessed(url);
    end);
  
  // 建立管道
  FHttpGetInput := FPipeline.MakePipe<string>(100);
  FHtmlParseInput := FPipeline.MakePipe<THttpPage>(10);
  
  // 設定階段
  FPipeline.Stage('Unique filter',
    procedure
    begin
      Asy_UniqueFilter(baseUrl, FPipeline.Input, FHttpGetInput);
    end);
  
  for i := 1 to TThread.ProcessorCount do
    FPipeline.Stage<string, THttpPage>('Http get #' + i.ToString, FHttpGetInput, FHtmlParseInput, Asy_HttpGet);
  
  FPipeline.Stage<THttpPage, string>('Html parser', FHtmlParseInput, FPipeline.Input, Asy_HtmlParse);
  
  FPageCount := 1;
  FPipeline.Input.Write(baseUrl);
end;

管線運作機制

管線的運作機制如下:

  • 當使用者呼叫 Start 方法時,管線會被建立並啟動。
  • 過濾階段接收初始 URL 並檢查是否已經處理過,若未處理則傳遞給下載階段。
  • 下載階段接收 URL 並下載其內容,若成功則將 URL 和 HTML 傳遞給解析階段。
  • 解析階段解析 HTML 並提取相關 URL,將其傳回過濾階段進行處理。
  • 當網頁處理完成後,OnPageProcessed 事件會被觸發。
  • 當整個處理過程完成後,OnFinished 事件會被觸發。

管線優點與挑戰

使用管線架構來實作網路蜘蛛具有以下優點:

  • 模組化:管線架構將整個處理過程分解為多個獨立的階段,使得程式碼更容易維護和擴充。
  • 平行處理:管線架構可以輕易地實作平行處理,提高程式的執行效率。

然而,管線架構也面臨一些挑戰:

  • 死鎖問題:當管線中的某個階段被阻塞時,可能會導致整個管線死鎖。
  • 資源管理:管線架構需要妥善管理資源,避免資源浪費或不足。

圖表說明

此圖示展示了網路蜘蛛管線的內部結構:

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 圖表說明

rectangle "URL" as node1
rectangle "HTML" as node2

node1 --> node2

@enduml

圖表翻譯: 此圖表呈現了網路蜘蛛管線的三個主要階段及其之間的資料流動。過濾階段接收 URL 並將其傳遞給下載階段,下載階段下載 HTML 內容並將其傳遞給解析階段,解析階段提取相關 URL 並將其傳回過濾階段。

程式碼解析

FPipeline.Stage('Unique filter',
  procedure
  begin
    Asy_UniqueFilter(baseUrl, FPipeline.Input, FHttpGetInput);
  end);

內容解密:

此段程式碼設定了管線的第一個階段:過濾階段。Asy_UniqueFilter 程式負責檢查 URL 是否已經被處理過,如果沒有,則將其傳遞給下載階段。這個階段確保了相同的 URL 不會被重複處理。

for i := 1 to TThread.ProcessorCount do
  FPipeline.Stage<string, THttpPage>('Http get #' + i.ToString, FHttpGetInput, FHtmlParseInput, Asy_HttpGet);

內容解密:

這段程式碼根據系統的處理器數量建立多個下載階段。每個下載階段都會呼叫 Asy_HttpGet 程式來下載指定的 URL 對應的網頁內容。這樣可以充分利用多核心處理器的能力,提高下載效率。

FPipeline.Stage<THttpPage, string>('Html parser', FHtmlParseInput, FPipeline.Output, Asy_HtmlParse);

內容解密:

這段程式碼設定了解析階段。Asy_HtmlParse 程式負責解析下載下來的 HTML 內容,提取出其中的 URL,並將結果輸出到管線的輸出端。這個階段是將下載的內容轉換為有用的資料的關鍵步驟。

網路蜘蛛的應用與未來發展

網路蜘蛛技術廣泛應用於搜尋引擎、資料採集、網路監控等領域。隨著網路技術的不斷發展,網路蜘蛛技術也在不斷進步,例如採用更先進的網頁解析技術、提升反爬蟲策略等。未來,網路蜘蛛技術可能會與人工智慧、大資料等技術結合,實作更智慧、更高效的資料採集和分析。