# <4> 並列プログラミングライブラリ (PPL) (Delphi コンカレントプログラミング) --- tags: Delphi programming Pascal embarcadero objectpascal created_at: 2021-12-04 updated_at: 2021-12-11 --- # 4. 並列プログラミングライブラリ (PPL) クロスプラットフォームで使える**並列プログラミングライブラリ** (**P**arallel **P**rogramming **L**ibrary / **PPL**) は `System.Threading` を **uses** に加えることで利用可能になります [^1]。 この並列プログラミングライブラリはマルチスレッドを抽象化しており、`TThread` を使う事なくマルチスレッドを利用可能です。 ## 4.1. スレッドプール 例えば 1 万ファイルをスレッドで処理するのに、1 万スレッド生成するのは効率の面で問題があります。生成されるスレッドの上限を決めておき、同時実行されるスレッドの管理を行う機構がスレッドプールです。いわゆる **Worker Thread** です。 Delphi の PPL では、CPU の負荷に応じて自動的にワーカースレッドが作成されます。 ワーカースレッドは `System.Threading.TThreadPool` クラスが管理しています。 `TThreadPool` クラスをそのまま使う事はまずないかと思います。 **See also:** - [System.Threading.TThreadPool (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Threading.TThreadPool) - [並列プログラミングでのスレッドプーリングの概要 (Support Wiki)](https://docwiki.embarcadero.com/Support/ja/%E4%B8%A6%E5%88%97%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%A7%E3%81%AE%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89%E3%83%97%E3%83%BC%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%AE%E6%A6%82%E8%A6%81) - [スレッドプール (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/windows/win32/procthread/thread-pools) - [マネージドスレッドプール (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/dotnet/standard/threading/the-managed-thread-pool) - [Thread pool (Wikipedia: en)](https://en.wikipedia.org/wiki/Thread_pool) ## 4.2. TTask コードブロックを簡単にスレッドで実行することができます。 ```pascal uses ..., System.Threading; ... TTask.Run(procedure begin // 何かの処理 TThread.Synchronize(nil, procedure begin // 何かの処理 end); end); ``` コンストラクタと `Start()` メソッドを使って `TThread.CreateAnonymousThread` のように使う事もできます。 ```pascal TTask.Create(procedure begin // 何かの処理 TThread.Synchronize(nil, procedure begin // 何かの処理 end); end).Start; ``` インターフェイス型変数に取って処理する事もできます。次のコードの `Task` 変数はインターフェイス型変数なので、明示的な破棄は不要です。 ```pascal var Task: ITask; ... Task := TTask.Create(procedure begin // 何かの処理 TThread.Synchronize(nil, procedure begin // 何かの処理 end); end); Task.Start; ``` `WaitForAll()` を使うと、ITask 配列で処理されるスレッドの終了待ちを行う事ができます。 ```pascal var TaskArr: array of ITask; begin Setlength (TaskArr ,3); TaskArr[0] := TTask.Run(procedure begin {何かの処理} end); TaskArr[1] := TTask.Run(procedure begin {何かの処理} end); TaskArr[2] := TTask.Run(procedure begin {何かの処理} end); TTask.WaitForAll(TaskArr); end; ``` もっとシンプルに書くこともできます。 ```pascal TTask.WaitForAll([ TTask.Run(procedure begin {何かの処理} end), TTask.Run(procedure begin {何かの処理} end), TTask.Run(procedure begin {何かの処理} end) ]); ``` タスクを停止させるには、`Cancel()` メソッドを使います。 ```pascal procedure TForm1.Button1Click(Sender: TObject); begin var Task := TTask.Run( procedure begin TThread.Sleep(2000); TThread.Synchronize(nil, procedure begin Button1.Caption := 'Done.'; // ボタンを押して 2 秒後にボタンのキャプションが... end); end); Task.Cancel; // 書き変わらない (w end; ``` **See also:** - [System.Threading.TTask (DocWiki)](https://docwiki.embarcadero.com/Libraries/Sydney/ja/System.Threading.TTask) - [System.Threading.TTask.Run (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Threading.TTask.Run) - [System.Threading.TTask.Create (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Threading.TTask.Create) - [System.Threading.ITask.Start (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Threading.ITask.Start) - [System.Threading.ITask.Cancel (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Threading.ITask.Cancel) - [System.Threading.TTask.WaitForAll (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Threading.TTask.WaitForAll) - [チュートリアル:並列プログラミング ライブラリのタスクを使用する (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%EF%BC%9A%E4%B8%A6%E5%88%97%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0_%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E3%82%BF%E3%82%B9%E3%82%AF%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B) - [Delphi でコントロール配列 \[小ネタ\] (Qiita)](./9bf3b2e761cc4f169c75.md) ## 4.3. TTask.Future いわゆる **Future** です。意訳すると **"引換券"** です。焼き芋屋さんに「焼いといて!」とお願いして、引換券をもらった感じです。すぐに引き換えに行っても「まだ焼けてないよ!」と待たされることになりますが、ちょっと経ってから行くとすぐに焼き芋を貰えます。 VCL フォームアプリケーションを新規作成し、フォームにボタンを置いて、次のようなコードを書きます。 ```pascal unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Threading; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; Future: IFuture; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage(Future.Value.ToString) end; initialization Future := TTask.Future( function: Integer begin TThread.Sleep(5000); Result := 100; end); end. ``` アプリケーションを起動してすぐにボタンを押すと結果が表示されるまでちょっと待たされますが、アプリケーションを起動して 5 秒以上待ってからボタンを押すと結果がすぐに表示されます。 コード中の `Future` はインターフェイス型変数なので、明示的な破棄は不要です。 :::note info 『Delphi クイックリファレンス』には TThread を使った TFuture の実装が載っています。 Ray Lischner (著), 光田 秀, 竹田 知生 (訳) (2001).「Delphi クイックリファレンス」オライリージャパン. pp.174-184. ::: **See also:** - [System.Threading.TTask.Future (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Threading.TTask.Future) - [System.Threading.IFuture (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Threading.IFuture) - [チュートリアル:並列プログラミング ライブラリのフューチャを使用する (DocWiki)](https://docwiki.embarcadero.com/RADStudio/Sydney/ja/%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%EF%BC%9A%E4%B8%A6%E5%88%97%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0_%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E3%83%95%E3%83%A5%E3%83%BC%E3%83%81%E3%83%A3%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B) - [Future / Promise / Delay パターン (Wikipedia)](https://ja.wikipedia.org/wiki/Future_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3) ## 4.4. TParallel.For ものすごくざっくり言えば **for to do** 文の並列実行版です。一つのコードブロックを繰り返し並列実行します。 オーバーロードされたメソッドが多いので混乱しますが、最もシンプルなものは次のようなパラメータを持ちます。 ```pascal TParallel.For(開始値, 終了値, 実行する手続きへの参照型); ``` 「実行する手続き」のうち最もシンプルなのは `TProc` です。`i: Integer` のような Integer 型のパラメータを一つ持つ手続きです。パラメータ名は `i` でなくとも任意の識別子で構いません。 ```pascal uses ..., System.Threading; ... // シーケンシャル実行 for var i:=1 to 10 do begin // ここのコードが繰り返し (直列) 実行される。 end; // パラレル実行 TTask.Run(procedure begin TParallel.For(1, 10, procedure (i: Integer) begin // ここのコードがスレッドで並列実行される。 end); end; ``` `TParallel.For()` を `TTask` 内で実行している理由の一つは `TParallel.For()` はメインスレッドで実行されると `TThread.Synchronize()` をブロックするからです。 ```pascal // 本当に固まるので実行注意! procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Clear; Memo1.Update; TParallel.For(1, 10, procedure (i: Integer) begin TThread.Sleep(5000); TThread.Synchronize(nil, procedure begin Memo1.Lines.Add(i.ToString); end); end); end; ``` `TThread.Queue()` だとブロックされないのですが、すべてのスレッドを実行し終わるまで (終了ではありません) はフォームを移動できません。 ```pascal procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Clear; Memo1.Update; TParallel.For(1, 10, procedure (i: Integer) begin TThread.Sleep(5000); TThread.Queue(nil, procedure begin Memo1.Lines.Add(i.ToString); end); end); end; ``` ...なので、`TParallel.For()` は基本的に `TTask` または、 ```pascal procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Clear; Memo1.Update; TTask.Run(procedure begin TParallel.For(1, 10, procedure (i: Integer) begin TThread.Sleep(5000); TThread.Synchronize(nil, procedure begin Memo1.Lines.Add(i.ToString); end); end) end); end; ``` `TThread` と併用する必要があります。 ```pascal procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Clear; Memo1.Update; TThread.CreateAnonymousThread(procedure begin TParallel.For(1, 10, procedure (i: Integer) begin TThread.Sleep(5000); TThread.Synchronize(nil, procedure begin Memo1.Lines.Add(i.ToString); end); end) end).Start; end; ``` :::note warn 10.1 Berlin と 10.2 Tokyo には TParallel.For() が最初の 2 ループを同じスレッドで実行してしまうというバグがあります。 ::: **See also:** - [System.Threading.TParallel.For (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Threading.TParallel.For) - [並列プログラミング ライブラリの TParallel.For の使用 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E4%B8%A6%E5%88%97%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0_%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE_TParallel.For_%E3%81%AE%E4%BD%BF%E7%94%A8) - [チュートリアル:並列プログラミング ライブラリの for ループを使用する (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB%EF%BC%9A%E4%B8%A6%E5%88%97%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0_%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE_for_%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B) - [RSP-20695: Parallel For or Join with n iterations runs in n-1 threads (Quality Portal)](https://quality.embarcadero.com/browse/RSP-20695) ## 4.5. TParallel.Join `TParallel.Join` は、複数のコードブロックを並列実行します。 最もシンプルなものは次のようなパラメータを持ちます。 ```pascal TParallel.Join(実行する手続きへの参照型の配列); ``` `TTask.WaitForAll()` もそうでしたが、`TParallel.Join()` を無名メソッドで書く事はあまりないかもしれません。 ```pascal procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Clear; Memo1.Update; TParallel.Join([ procedure begin TThread.Synchronize(nil, procedure begin Memo1.Lines.Add('A'); end); end, procedure begin TThread.Synchronize(nil, procedure begin Memo1.Lines.Add('B'); end); end, procedure begin TThread.Synchronize(nil, procedure begin Memo1.Lines.Add('C'); end); end ]); end; ``` 好みの問題でしょうが、個人的には手続きは独立させておいた方が視認性が良いと感じます。 ```pascal procedure TForm1.A; begin TThread.Synchronize(nil, procedure begin Memo1.Lines.Add('A'); end); end; procedure TForm1.B; begin TThread.Synchronize(nil, procedure begin Memo1.Lines.Add('B'); end); end; procedure TForm1.C; begin TThread.Synchronize(nil, procedure begin Memo1.Lines.Add('C'); end); end; procedure TForm1.Button1Click(Sender: TObject); begin Memo1.Clear; Memo1.Update; TParallel.Join([A, B, C]); end; ``` :::note warn 10.1 Berlin と 10.2 Tokyo には TParallel.Join() が最初の 2 つのタスクを同じスレッドで実行してしまうというバグがあります。 ::: **See also:** - [System.Threading.TParallel.Join (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Threading.TParallel.Join) - [RSP-19557: TParallel.Join does not create enough threads (Quality Portal)](https://quality.embarcadero.com/browse/RSP-19557) # 参考 - [並列プログラミング ライブラリの使用 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E4%B8%A6%E5%88%97%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0_%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E4%BD%BF%E7%94%A8) - [RAD Studio XE7 - the new Parallel Programming Library (Youtube)](https://www.youtube.com/watch?v=x8S5b0peu7U) - [Parallel Programming Library (Delphi) (Youtube)](https://www.youtube.com/watch?v=Ni3JDxNFiiw) - [RAD Studio XE7の新機能 - パラレルライブラリ・動的配列 (Youtube)](https://www.youtube.com/watch?v=o5I4X5GU4Dk) - [コードサンプル: ライフゲーム (VCL) (docwiki)](https://docwiki.embarcadero.com/CodeExamples/en/RTL.ConwaysLifeVCL_Sample) - [コードサンプル: ライフゲーム (FireMonkey) (docwiki)](http://docwiki.embarcadero.com/codeExamples/en/RTL.ConwaysLifeFMX_Sample) - [タスク並列ライブラリ (TPL) (docs.microsoft.com)](https://docs.microsoft.com/ja-jp/dotnet/standard/parallel-programming/task-parallel-library-tpl) - [CodeRage 9: Parallel Programming Library: Create Responsive Object Pascal Apps (Youtube)](https://www.youtube.com/watch?v=rZfux4by0po) - [Delphi - Multi threading (Chau Chee Yang Technical Blog)](http://chee-yang.blogspot.com/2015/12/delphi-multi-threading_4.html) - [Delphi の TTask でも ContinueWith がしたい! (Qiita: @sagiri )](https://qiita.com/sagiri/items/e37533449f2b9be4664f) # 索引 [ [← 3. スレッドローカル変数 (スレッドローカルストレージ)](./09f04b5768135401eebb.md) ] [ [↑ 目次へ](https://qiita.com/items/e8c1ff3a4c74e4c2a4f3) ] [ [→ 5. サードパーティ製ライブラリ](./e7f655bb73af05fbac35.md) ] [^1]: `System.Threading` は Delphi XE7 以降で利用可能です。