Sign in Join
Wiki copytemporary

Multithreaded Application Tutorial/ja: what changed

Revision 3 to 4. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
1+**日本語版メニュー**
2+---
3+[[Main Page/ja|メインページ]] \- [[Lazarus Documentation/ja|Lazarus Documentation日本語版]] \- [[Japanese Localization Notes|翻訳ノート]] \- [[Bug Tracking/ja|日本語障害情報]]
4+
5+
16## 概要
27
3-このページでは Free Pascal と Lazarus を使ってマルチスレッドアプリケーションを記述したりデバッグしたりする方法について述べます。マルチスレッドアプリケーションは、同時作業させるために複数のスレッドを作成しているものを指します。もし、読者がマルチスレッドに初めて触れるのなら、是非マルチスレッドが本当に必要かどうかを「マルチスレッドが必要ですか?」節でお確かめください。そうすれば頭痛の種を激減することができるでしょう。
8+このページでは Free Pascal と Lazarus を使ってマルチスレッドアプリケーションを記述したりデバッグしたりする方法について述べます。マルチスレッドアプリケーションは、同時作業させるために複数のスレッドを作成しているものを指します。もし、読者がマルチスレッドに初めて触れるのなら、是非マルチスレッドが本当に必要かどうかを「マルチスレッドが必要ですか?」節でお確かめください。そうすれば頭痛の種を激減することができるでしょう。
49
5-スレッドのうち 1 つは、メインスレッドと呼ばれるものです。メインスレッドはアプリケーションが起動する際、オペレーティングシステムによって生成されます。メインスレッドはユーザインターフェイス、コンポーネントを更新する唯一のスレッドです。メインスレッドは**かならず**ただ一つである必要があり、そうでないとアプリケーションは暴走します。
10+スレッドのうち 1 つは、メインスレッドと呼ばれるものです。メインスレッドはアプリケーションが起動する際、オペレーティングシステムによって生成されます。メインスレッドはユーザインターフェイス、コンポーネントを更新する唯一のスレッドです。メインスレッドは**かならず** ただ一つである必要があり、そうでないとアプリケーションは暴走します。
611
7-マルチスレッドプログラミングの基本的な考え方は、ユーザがメインスレッドを通して作業する傍ら、もう一つのスレッドを用いてある種の処理をバックグラウンドで行えるようにアプリケーションを組むという点です。
12+マルチスレッドプログラミングの基本的な考え方は、ユーザがメインスレッドを通して作業する傍ら、もう一つのスレッドを用いてある種の処理をバックグラウンドで行えるようにアプリケーションを組むという点です。
813
9-スレッドの他の使い方として、アプリケーションの応答速度を改善することが挙げられます。例えば、あなたがアプリケーションを作られたとして、ユーザがボタンを押してアプリケーションがすごく重たい処理を開始したとします。処理の間、スクリーンは凍りつき反応が無くなり、ユーザはアプリケーションが落ちたと思うでしょう。よくないことです。もし、その重たい処理を 2 つ目のスレッドの中で実行するようにすれば、アプリケーションの応答性は保たれ、まるでそんな処理は行っていないかのように振舞います。このような場合には、スレッドを開始する前にフォームの処理を開始するボタンを使用不可能にしておくと、ユーザがそれ以上の作業を要求することを無くすことができ、好都合です。
14+スレッドの他の使い方として、アプリケーションの応答速度を改善することが挙げられます。例えば、あなたがアプリケーションを作られたとして、ユーザがボタンを押してアプリケーションがすごく重たい処理を開始したとします。処理の間、スクリーンは凍りつき反応が無くなり、ユーザはアプリケーションが落ちたと思うでしょう。よくないことです。もし、その重たい処理を 2 つ目のスレッドの中で実行するようにすれば、アプリケーションの応答性は保たれ、まるでそんな処理は行っていないかのように振舞います。このような場合には、スレッドを開始する前にフォームの処理を開始するボタンを使用不可能にしておくと、ユーザがそれ以上の作業を要求することを無くすことができ、好都合です。
1015
11-また、サーバアプリケーションを作る際に、同時に大量のクライアントに応答しなければならない場合にも使えます。
16+また、サーバアプリケーションを作る際に、同時に大量のクライアントに応答しなければならない場合にも使えます。
1217
1318## マルチスレッドが必要ですか?
1419
15-もし読者がマルチスレッドにこれまで触れたことがなく、単に重い処理を実行するアプリケーションの応答性を改善したいだけなら、マルチスレッドはお探しのものとは違うかもしれません。マルチスレッドアプリケーションはしばしば複雑であり、デバッグは常に困難です。また、多くの場合マルチスレッドは必要のないものです。単一スレッドアプリケーションで十分です。時間のかかるタスクを細かな部分に分割することができるなら、マルチスレッドの代わりに **Application.ProcessMessages** を使うべきです。このメソッドを使用すると、LCL に待機中のメッセージをすべて処理させたのち制御が戻ってきます。分割したタスクの一部分を実行したら Application.ProcessMessages を呼び、ユーザがアプリケーションを終了させたりどこかをクリックしたりしたかを確認し、あるいは進行状況表示を再描画したりします。その後タスクの次の部分を実行し、また Application.ProcessMessages を呼びます。
20+もし読者がマルチスレッドにこれまで触れたことがなく、単に重い処理を実行するアプリケーションの応答性を改善したいだけなら、マルチスレッドはお探しのものとは違うかもしれません。マルチスレッドアプリケーションはしばしば複雑であり、デバッグは常に困難です。また、多くの場合マルチスレッドは必要のないものです。単一スレッドアプリケーションで十分です。時間のかかるタスクを細かな部分に分割することができるなら、マルチスレッドの代わりに **Application.ProcessMessages** を使うべきです。このメソッドを使用すると、LCL に待機中のメッセージをすべて処理させたのち制御が戻ってきます。分割したタスクの一部分を実行したら Application.ProcessMessages を呼び、ユーザがアプリケーションを終了させたりどこかをクリックしたりしたかを確認し、あるいは進行状況表示を再描画したりします。その後タスクの次の部分を実行し、また Application.ProcessMessages を呼びます。
1621
17-例:大きなファイルの読み込みと処理\
18-examples/multithreading/singlethreadingexample1.lpi
22+例:大きなファイルの読み込みと処理
23+examples/multithreading/singlethreadingexample1.lpi
1924
20-マルチスレッドが必要なのは、次のような場合だけです。
25+マルチスレッドが必要なのは、次のような場合だけです。
2126
22-- 同期型のネットワーク通信のような、ブロッキングタイプの処理の場合(訳注:同期型の場合タイムアウトするまで処理が戻ってこない可能性がありますから、例えば別スレッドでキャンセル待ち処理をするような使い方が考えられます)。
23-- マルチプロセッサによる同時処理(SMP)の場合。
24-- API を呼び出さなければならないためにそれ以上細かく分割できないアルゴリズムやライブラリの呼び出し(訳注:分割できない長い処理を普通に行うとハングアップして固まったかのように見えてしまいますから、時間がかかる処理は別スレッドで行い描画やウィンドウの移動などの操作にはメインスレッドで反応するような使い方が考えられます)。
27+ * 同期型のネットワーク通信のような、ブロッキングタイプの処理の場合(訳注:同期型の場合タイムアウトするまで処理が戻ってこない可能性がありますから、例えば別スレッドでキャンセル待ち処理をするような使い方が考えられます)。
28+ * マルチプロセッサによる同時処理(SMP)の場合。
29+ * API を呼び出さなければならないためにそれ以上細かく分割できないアルゴリズムやライブラリの呼び出し(訳注:分割できない長い処理を普通に行うとハングアップして固まったかのように見えてしまいますから、時間がかかる処理は別スレッドで行い描画やウィンドウの移動などの操作にはメインスレッドで反応するような使い方が考えられます)。
2530
26-マルチプロセッサによる同時処理で処理速度を上げるためにマルチスレッドを使いたいのであれば、プログラムが 1 つの CPU コアのリソースを 100% フルに使い切っているか確認してください(例えば、プログラムでファイルの書き込みなど入出力の処理が多く占めている場合、時間がかかる作業ではありますが CPU には負荷はかかっていません。このようなケースでマルチスレッドを使ってもプログラムは速くならないでしょう。)。また、最適化レベルが最大の 3 なっているかも確認してください。最適化レベルを 1 から 3 に切り替えたら、プログラムが約 5 倍速くなったことがありました。
31+マルチプロセッサによる同時処理で処理速度を上げるためにマルチスレッドを使いたいのであれば、プログラムが 1 つの CPU コアのリソースを 100% フルに使い切っているか確認してください(例えば、プログラムでファイルの書き込みなど入出力の処理が多く占めている場合、時間がかかる作業ではありますが CPU には負荷はかかっていません。このようなケースでマルチスレッドを使ってもプログラムは速くならないでしょう。)。また、最適化レベルが最大の 3 なっているかも確認してください。最適化レベルを 1 から 3 に切り替えたら、プログラムが約 5 倍速くなったことがありました。
2732
2833## マルチスレッドアプリケーションで必要となるユニット
2934
30-Windows では動作させるのに必要なユニットは特にありません。Linux、Mac OS X、FreeBSD では cthreads ユニットが必要で、プロジェクトユニット(プログラムソース、通常は .lpr ファイル)の一番最初に*必ず*置かなくてなりません!
31-
32-Lazarus アプリケーションコードでは以下のようになるでしょう。(訳注:実際に見れば分かりますが、cthreads は自動生成された時点で既に UseCThreads スイッチ付きで組み込まれています。)
35+Windows では動作させるのに必要なユニットは特にありません。Linux、Mac OS X、FreeBSD では cthreads ユニットが必要で、プロジェクトユニット(プログラムソース、通常は .lpr ファイル)の一番最初に _必ず_ 置かなくてなりません!
3336
34-``` pascal
37+Lazarus アプリケーションコードでは以下のようになるでしょう。(訳注:実際に見れば分かりますが、cthreads は自動生成された時点で既に UseCThreads スイッチ付きで組み込まれています。)
38+```
3539program MyMultiThreadedProgram;
36-{$mode objfpc}{$H+}
37-uses
38-{$ifdef unix}
39- cthreads,
40- cmem, // the c memory manager is on some systems much faster for multi-threading
41-{$endif}
42- Interfaces, // this includes the LCL widgetset
43- Forms
44- { you can add units here },
40+ {$mode objfpc}{$H+}
41+ uses
42+ {$ifdef unix}
43+ cthreads,
44+ cmem, // the c memory manager is on some systems much faster for multi-threading
45+ {$endif}
46+ Interfaces, // this includes the LCL widgetset
47+ Forms
48+ { you can add units here },
4549```
4650
47-これを怠ると、起動時に以下のエラーが出ます。
48-
49-``` pascal
50- This binary has no thread support compiled in.
51- Recompile the application with a thread-driver in the program uses clause before other units using thread.
52- 訳:このバイナリはスレッドをサポートしないでコンパイルされています。
53- スレッドドライバをスレッドを使用されているユニットより前のプログラム uses 節に置き、アプリケーションをコンパイルし直してください。
51+これを怠ると、起動時に以下のエラーが出ます。
5452```
53+This binary has no thread support compiled in.
54+ Recompile the application with a thread-driver in the program uses clause before other units using thread.
55+ 訳:このバイナリはスレッドをサポートしないでコンパイルされています。
56+ スレッドドライバをスレッドを使用されているユニットより前のプログラム uses 節に置き、アプリケーションをコンパイルし直してください。
57+```
5558
56-> **Note:** "mcount" が見つからないというようなリンカエラーが発生することがあります。これが発生するのは、マルチスレッドコードを含むユニットを使っていて、cthreads ユニットの追加もしくはスマートリンクの使用が必要な場合です。
59+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** "mcount" が見つからないというようなリンカエラーが発生することがあります。これが発生するのは、マルチスレッドコードを含むユニットを使っていて、cthreads ユニットの追加もしくはスマートリンクの使用が必要な場合です。
5760
58-> **Note:** 手続き SYSTEM_NOTHREADERROR 内でエラー "Project raised exception class 'RunError(232)'" が発生することがあります。これが発生するのは、コード内でスレッドが必要としており、cthreads ユニットの追加が必要な場合です。
61+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** 手続き SYSTEM_NOTHREADERROR 内でエラー "Project raised exception class 'RunError(232)'" が発生することがあります。これが発生するのは、コード内でスレッドが必要としており、cthreads ユニットの追加が必要な場合です。
5962
6063## TThread クラス
6164
62-サンプルは「examples/multithreading/」にあります。
63-
64-マルチスレッドアプリケーションは TThread クラスを使用すると簡単に作成できます。このクラスを用いると追加のスレッド(メインスレッドと並列に実行する)を作ることができます。そのためには通常、Create コンストラクタと、Execute メソッドの 2 つのメソッドをオーバーライドしなければなりません。
65+サンプルは「examples/multithreading/」にあります。
6566
66-コンストラクタは、スレッドを準備する際に使います。必要となる変数ないしプロパティに初期値を設定します。基となる TThread のコンストラクタは Suspended という引数を必要とします。名前からもお分かりのように、Suspended が False の場合、スレッドは作成後直ちに実行されます。逆に Suspended が True の場合、スレッドは停止状態で作成されます。この場合、スレッドを実行するには Start メソッドを呼びます。
67+マルチスレッドアプリケーションは TThread クラスを使用すると簡単に作成できます。このクラスを用いると追加のスレッド(メインスレッドと並列に実行する)を作ることができます。そのためには通常、Create コンストラクタと、Execute メソッドの 2 つのメソッドをオーバーライドしなければなりません。
6768
68-> **Note:** メソッド Resume は [FPC 2.4.4 以降非推奨になりました](http://wiki.freepascal.org/User_Changes_2.4.4#TThread.Suspend_and_TThread.Resume_have_been_deprecated)。Resume は Start に置き換えてください。
69+コンストラクタは、スレッドを準備する際に使います。必要となる変数ないしプロパティに初期値を設定します。基となる TThread のコンストラクタは Suspended という引数を必要とします。名前からもお分かりのように、Suspended が False の場合、スレッドは作成後直ちに実行されます。逆に Suspended が True の場合、スレッドは停止状態で作成されます。この場合、スレッドを実行するには Start メソッドを呼びます。
6970
70-FPC バージョン 2.0.1 以降の場合、TThread.Create は StackSize という暗黙のパラメータを持っています。必要であれば作成されたスレッドのデフォルトスタックサイズを変更することが可能です。例えばスレッド内で深い再帰呼び出しを行う際には重宝するでしょう。スタックサイズを指定しなかった場合は、OS による規定のスタックサイズが使用されます。
71+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** メソッド Resume は [[User Changes 2.4.4|FPC 2.4.4 以降非推奨になりました]]。Resume は Start に置き換えてください。
7172
72-スレッドで実行したいコードは、Execute メソッドをオーバーライドして、その中に書きます。
73+FPC バージョン 2.0.1 以降の場合、TThread.Create は StackSize という暗黙のパラメータを持っています。必要であれば作成されたスレッドのデフォルトスタックサイズを変更することが可能です。例えばスレッド内で深い再帰呼び出しを行う際には重宝するでしょう。スタックサイズを指定しなかった場合は、OS による規定のスタックサイズが使用されます。
7374
74-TThread クラスは一つの重要なプロパティを持っています。それは、
75+スレッドで実行したいコードは、Execute メソッドをオーバーライドして、その中に書きます。
7576
76-```text
77-Terminated : boolean;
77+TThread クラスは一つの重要なプロパティを持っています。それは、
7878```
79-
80-です。
81-
82-Terminated のデフォルト値は False です。通常の場合、スレッドはループを持っていますが、Terminated が True になったら、そのループから脱出しなければなりません。したがってループのサイクル毎に、Terminated が True になっていないかチェックする必要があります。もし True になっていたなら、速やかに必要な終了処理を行い、Execute メソッドを終了させなければなりません。ですから、Terminate メソッドを呼び出してもデフォルト状態では何も起きないことを、しっかり覚えておいてください。Execute メソッド自体が自分自身を終了するように明示的に実装しなければなりません。
83-
84-先に説明したように、スレッドはビジュアルコンポーネントを操作すべきではありません。なにかをユーザに表示するためにはメインスレッドで行います。
79+Terminated : boolean;
80+```
8581
86-これを行うために TThread には Synchronize というメソッドがあります。このメソッドは引数として、引数を持たないメソッドを 1 つとります。例えば、MyMethod というメソッドをスレッドから実行するには、Synchronize(@MyMethod) として呼び出します。この時スレッドの実行は一時的に停止し、メインスレッドから当該メソッドが実行され、その後スレッド実行が再開されます。
82+です。
8783
88-Synchronize の正確な動作はプラットフォーム依存しますが、基本的には以下のようになります。
84+Terminated のデフォルト値は False です。通常の場合、スレッドはループを持っていますが、Terminated が True になったら、そのループから脱出しなければなりません。したがってループのサイクル毎に、Terminated が True になっていないかチェックする必要があります。もし True になっていたなら、速やかに必要な終了処理を行い、Execute メソッドを終了させなければなりません。ですから、Terminate メソッドを呼び出してもデフォルト状態では何も起きないことを、しっかり覚えておいてください。Execute メソッド自体が自分自身を終了するように明示的に実装しなければなりません。
8985
90-- メッセージをメインメッセージキューに投げ、スレッドは休眠状態になります。
91-- メインスレッドでメッセージが処理され、MyMethod が呼ばれます。MyMethod は(デバイスや描画用の)コンテクスト無しで実行されます。つまり、MouseDown イベントや Paint イベント実行中に割り込んで実行されるのではなく、これらのイベントは後で実行されます。
92-- メインスレッドで MyMethod が実行された後、スレッドの休眠状態が解かれ、次のメッセージが処理されます。
93-- スレッドの処理が再開されます。
86+先に説明したように、スレッドはビジュアルコンポーネントを操作すべきではありません。なにかをユーザに表示するためにはメインスレッドで行います。
9487
95-TThread が持つもう一つの重要なプロパティに FreeOnTerminate があります。このプロパティを True にしておくと、スレッドの実行(Execute メソッド)が停止した後にスレッドオブジェクトは自動的に解放されます。これを行わない場合は、アプリケーション側で手動でオブジェクトを解放する必要があります。\
96-(訳注:以下の例では単に FreeOnTerminate = True しているだけなので、スレッドが終了した場合 MyThread 変数が示すアドレスに不正にアクセスできてしまいます。スレッド終了時に必ず呼ばれる TThread.OnTerminate で MyThread := nil するなどして適切な処置を行ってください。OnTerminate イベントも Synchronize を通して呼ばれています。)
88+これを行うために TThread には Synchronize というメソッドがあります。このメソッドは引数として、引数を持たないメソッドを 1 つとります。例えば、MyMethod というメソッドをスレッドから実行するには、Synchronize(@MyMethod) として呼び出します。この時スレッドの実行は一時的に停止し、メインスレッドから当該メソッドが実行され、その後スレッド実行が再開されます。
9789
98-例:
90+Synchronize の正確な動作はプラットフォーム依存しますが、基本的には以下のようになります。
9991
100-``` pascal
101- Type
102- TMyThread = class(TThread)
103- private
104- fStatusText : string;
105- procedure ShowStatus;
106- protected
107- procedure Execute; override;
108- public
109- Constructor Create(CreateSuspended : boolean);
110- end;
92+ * メッセージをメインメッセージキューに投げ、スレッドは休眠状態になります。
93+ * メインスレッドでメッセージが処理され、MyMethod が呼ばれます。MyMethod は(デバイスや描画用の)コンテクスト無しで実行されます。つまり、MouseDown イベントや Paint イベント実行中に割り込んで実行されるのではなく、これらのイベントは後で実行されます。
94+ * メインスレッドで MyMethod が実行された後、スレッドの休眠状態が解かれ、次のメッセージが処理されます。
95+ * スレッドの処理が再開されます。
11196
112- constructor TMyThread.Create(CreateSuspended : boolean);
113- begin
114- FreeOnTerminate := True;
115- inherited Create(CreateSuspended);
116- end;
97+TThread が持つもう一つの重要なプロパティに FreeOnTerminate があります。このプロパティを True にしておくと、スレッドの実行(Execute メソッド)が停止した後にスレッドオブジェクトは自動的に解放されます。これを行わない場合は、アプリケーション側で手動でオブジェクトを解放する必要があります。
98+(訳注:以下の例では単に FreeOnTerminate = True しているだけなので、スレッドが終了した場合 MyThread 変数が示すアドレスに不正にアクセスできてしまいます。スレッド終了時に必ず呼ばれる TThread.OnTerminate で MyThread := nil するなどして適切な処置を行ってください。OnTerminate イベントも Synchronize を通して呼ばれています。)
11799
118- procedure TMyThread.ShowStatus;
119- // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
120- begin
121- Form1.Caption := fStatusText;
122- end;
123-
124- procedure TMyThread.Execute;
125- var
126- newStatus : string;
127- begin
128- fStatusText := 'TMyThread Starting...';
129- Synchronize(@Showstatus);
130- fStatusText := 'TMyThread Running...';
131- while (not Terminated) and ([他のループ終了条件]) do
100+例:
101+```
102+Type
103+ TMyThread = class(TThread)
104+ private
105+ fStatusText : string;
106+ procedure ShowStatus;
107+ protected
108+ procedure Execute; override;
109+ public
110+ Constructor Create(CreateSuspended : boolean);
111+ end;
112+
113+ constructor TMyThread.Create(CreateSuspended : boolean);
132114 begin
133- ...
134- [ここにスレッドループの本体コードを記述します]
135- ...
136- if NewStatus <> fStatusText then
115+ FreeOnTerminate := True;
116+ inherited Create(CreateSuspended);
117+ end;
118+
119+ procedure TMyThread.ShowStatus;
120+ // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
121+ begin
122+ Form1.Caption := fStatusText;
123+ end;
124+
125+ procedure TMyThread.Execute;
126+ var
127+ newStatus : string;
128+ begin
129+ fStatusText := 'TMyThread Starting...';
130+ Synchronize(@Showstatus);
131+ fStatusText := 'TMyThread Running...';
132+ while (not Terminated) and ([他のループ終了条件]) do
137133 begin
138- fStatusText := newStatus;
139- Synchronize(@Showstatus);
134+ ...
135+ [ここにスレッドループの本体コードを記述します]
136+ ...
137+ if NewStatus <> fStatusText then
138+ begin
139+ fStatusText := newStatus;
140+ Synchronize(@Showstatus);
141+ end;
140142 end;
141143 end;
142- end;
143144```
144145
145-アプリケーション側では、
146-
147-``` pascal
148- var
149- MyThread : TMyThread;
150- begin
151- MyThread := TMyThread.Create(True); // このようにすると自動的に開始しません
152- ...
153- [ここにスレッドを実行する前に必要な初期化コードを記述します]
154- ...
155- MyThread.Start;
156- end;
146+アプリケーション側では、
157147```
158-
159-もっと柔軟な制御を望むなら、スレッドに対しイベントを作成します。こうすることで、イベントに対してイベントハンドラで設定できるようになり、Synchronize されたメソッドは特定のフォームやクラスに縛られなくなります。以下が例です。
160-
161-``` pascal
162- Type
163- TShowStatusEvent = procedure(Status: String) of Object;
164-
165- TMyThread = class(TThread)
166- private
167- fStatusText : string;
168- FOnShowStatus: TShowStatusEvent;
169- procedure ShowStatus;
170- protected
171- procedure Execute; override;
172- public
173- Constructor Create(CreateSuspended : boolean);
174- property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
175- end;
176-
177- constructor TMyThread.Create(CreateSuspended : boolean);
178- begin
179- FreeOnTerminate := True;
180- inherited Create(CreateSuspended);
181- end;
182-
183- procedure TMyThread.ShowStatus;
184- // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
185- begin
186- if Assigned(FOnShowStatus) then
187- begin
188- FOnShowStatus(fStatusText);
189- end;
190- end;
191-
192- procedure TMyThread.Execute;
193- var
194- newStatus : string;
195- begin
196- fStatusText := 'TMyThread Starting...';
197- Synchronize(@Showstatus);
198- fStatusText := 'TMyThread Running...';
199- while (not Terminated) and ([他のループ終了条件]) do
148+var
149+ MyThread : TMyThread;
200150 begin
151+ MyThread := TMyThread.Create(True); // このようにすると自動的に開始しません
201152 ...
202- [ここにスレッドループの本体コードを記述します]
153+ [ここにスレッドを実行する前に必要な初期化コードを記述します]
203154 ...
204- if NewStatus <> fStatusText then
155+ MyThread.Start;
156+ end;
157+```
158+
159+もっと柔軟な制御を望むなら、スレッドに対しイベントを作成します。こうすることで、イベントに対してイベントハンドラで設定できるようになり、Synchronize されたメソッドは特定のフォームやクラスに縛られなくなります。以下が例です。
160+```
161+Type
162+ TShowStatusEvent = procedure(Status: String) of Object;
163+
164+ TMyThread = class(TThread)
165+ private
166+ fStatusText : string;
167+ FOnShowStatus: TShowStatusEvent;
168+ procedure ShowStatus;
169+ protected
170+ procedure Execute; override;
171+ public
172+ Constructor Create(CreateSuspended : boolean);
173+ property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
174+ end;
175+
176+ constructor TMyThread.Create(CreateSuspended : boolean);
177+ begin
178+ FreeOnTerminate := True;
179+ inherited Create(CreateSuspended);
180+ end;
181+
182+ procedure TMyThread.ShowStatus;
183+ // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
184+ begin
185+ if Assigned(FOnShowStatus) then
186+ begin
187+ FOnShowStatus(fStatusText);
188+ end;
189+ end;
190+
191+ procedure TMyThread.Execute;
192+ var
193+ newStatus : string;
194+ begin
195+ fStatusText := 'TMyThread Starting...';
196+ Synchronize(@Showstatus);
197+ fStatusText := 'TMyThread Running...';
198+ while (not Terminated) and ([他のループ終了条件]) do
205199 begin
206- fStatusText := newStatus;
207- Synchronize(@Showstatus);
200+ ...
201+ [ここにスレッドループの本体コードを記述します]
202+ ...
203+ if NewStatus <> fStatusText then
204+ begin
205+ fStatusText := newStatus;
206+ Synchronize(@Showstatus);
207+ end;
208208 end;
209209 end;
210- end;
211210```
212211
213-アプリケーション側では、
214-
215-``` pascal
216- Type
217- TForm1 = class(TForm)
218- Button1: TButton;
219- Label1: TLabel;
220- procedure FormCreate(Sender: TObject);
221- procedure FormDestroy(Sender: TObject);
222- private
223- { private declarations }
224- MyThread: TMyThread;
225- procedure ShowStatus(Status: string);
226- public
227- { public declarations }
228- end;
229-
230- procedure TForm1.FormCreate(Sender: TObject);
231- begin
232- inherited;
233- MyThread := TMyThread.Create(true);
234- MyThread.OnShowStatus := @ShowStatus;
235- end;
236-
237- procedure TForm1.FormDestroy(Sender: TObject);
238- begin
239- MyThread.Terminate;
240-
241- // FreeOnTerminate が true であれば、以下の記述は必要ありません。
242- // MyThread.Free;
243- inherited;
244- end;
245-
246- procedure TForm1.Button1Click(Sender: TObject);
247- begin
248- MyThread.Start;
249- end;
250-
251- procedure TForm1.ShowStatus(Status: string);
252- begin
253- Label1.Caption := Status;
254- end;
212+アプリケーション側では、
255213```
214+Type
215+ TForm1 = class(TForm)
216+ Button1: TButton;
217+ Label1: TLabel;
218+ procedure FormCreate(Sender: TObject);
219+ procedure FormDestroy(Sender: TObject);
220+ private
221+ { private declarations }
222+ MyThread: TMyThread;
223+ procedure ShowStatus(Status: string);
224+ public
225+ { public declarations }
226+ end;
227+
228+ procedure TForm1.FormCreate(Sender: TObject);
229+ begin
230+ inherited;
231+ MyThread := TMyThread.Create(true);
232+ MyThread.OnShowStatus := @ShowStatus;
233+ end;
234+
235+ procedure TForm1.FormDestroy(Sender: TObject);
236+ begin
237+ MyThread.Terminate;
238+
239+ // FreeOnTerminate が true であれば、以下の記述は必要ありません。
240+ // MyThread.Free;
241+ inherited;
242+ end;
243+
244+ procedure TForm1.Button1Click(Sender: TObject);
245+ begin
246+ MyThread.Start;
247+ end;
248+
249+ procedure TForm1.ShowStatus(Status: string);
250+ begin
251+ Label1.Caption := Status;
252+ end;
253+```
256254
257255## 特に注意すべきこと
258256
259257### Windows におけるスタックチェック
260258
261-Windows で -Ct スイッチ(スタックチェック)を使うスレッドには頭の痛い問題があります。理由は定かではありませんが、デフォルトのスタックサイズを使う場合、スタックチェックが TTread.Create 内で問題を引き起こすことがあります。現在の運用上の回避策としては -Ct スイッチを使わないことです。一連の現象がメインスレッドで例外を引き起こすことは全くなく、新しく生成されたスレッド内で起こります。そのためスレッドが絶対開始できないように見えます。
262-
263-スレッド生成時に発生するその他の例外や、この問題をチェックする良いコードを示します。
259+Windows で -Ct スイッチ(スタックチェック)を使うスレッドには頭の痛い問題があります。理由は定かではありませんが、デフォルトのスタックサイズを使う場合、スタックチェックが TTread.Create 内で問題を引き起こすことがあります。現在の運用上の回避策としては -Ct スイッチを使わないことです。一連の現象がメインスレッドで例外を引き起こすことは全くなく、新しく生成されたスレッド内で起こります。そのためスレッドが絶対開始できないように見えます。
264260
265-``` pascal
261+スレッド生成時に発生するその他の例外や、この問題をチェックする良いコードを示します。
262+```
266263MyThread := TThread.Create(False);
267-if Assigned(MyThread.FatalException) then
268- raise MyThread.FatalException;
264+ if Assigned(MyThread.FatalException) then
265+ raise MyThread.FatalException;
269266```
270267
271-このコードは、スレッド生成時に発生するどんな例外でも、メインスレッドで生成させます。
268+このコードは、スレッド生成時に発生するどんな例外でも、メインスレッドで生成させます。
272269
273270### パッケージでのマルチスレッド
274271
275-マルチスレッドを使用しているパッケージには、**-dUseCThreads** フラグをカスタム使用オプションに追加してください。パッケージエディタを開いて、オプション → 使用 → カスタム、そこへ *-dUseCThreads* を追加します。これによって、このパッケージを使用している IDE を含めたすべてのプロジェクトおよびパッケージでこのフラグが定義されます。IDE および IDE によって作成されたアプリケーションには、.lpr ファイル内に以下のコードが組み込まれています。(訳注:cmem は自動生成で組み込まれてはいません(Lazarus 1.0.12/1.2RC1 で確認)。)
276-
277-``` pascal
272+マルチスレッドを使用しているパッケージには、**-dUseCThreads** フラグをカスタム使用オプションに追加してください。パッケージエディタを開いて、オプション → 使用 → カスタム、そこへ _-dUseCThreads_ を追加します。これによって、このパッケージを使用している IDE を含めたすべてのプロジェクトおよびパッケージでこのフラグが定義されます。IDE および IDE によって作成されたアプリケーションには、.lpr ファイル内に以下のコードが組み込まれています。(訳注:cmem は自動生成で組み込まれてはいません(Lazarus 1.0.12/1.2RC1 で確認)。)
273+```
278274uses
279- {$IFDEF UNIX}{$IFDEF UseCThreads}
280- cthreads,
281- cmem, // the c memory manager is on some systems much faster for multi-threading
282- {$ENDIF}{$ENDIF}
275+ {$IFDEF UNIX}{$IFDEF UseCThreads}
276+ cthreads,
277+ cmem, // the c memory manager is on some systems much faster for multi-threading
278+ {$ENDIF}{$ENDIF}
283279```
284280
285281### Heaptrc
286282
287-*cmem* ユニットを使った上で -gh スイッチを使うことはできません。-gh スイッチは、(メモリーリークを調べるために)ヒープマネージャーを拡張する heaptrc ユニットを使用するものです。ですから(リークチェックしたいのであれば)、**cmem** ユニットを使ってから **その後に** **heaptrc** ユニットを置くようにする必要があります。(訳注:heaptrc は cmem を考慮するように作られているけど、cmem はそうではないからでしょう。)
288-
289-``` pascal
283+_cmem_ ユニットを使った上で -gh スイッチを使うことはできません。-gh スイッチは、(メモリーリークを調べるために)ヒープマネージャーを拡張する heaptrc ユニットを使用するものです。ですから(リークチェックしたいのであれば)、**cmem** ユニットを使ってから **その後に** **heaptrc** ユニットを置くようにする必要があります。(訳注:heaptrc は cmem を考慮するように作られているけど、cmem はそうではないからでしょう。)
284+```
290285uses
291- {$IFDEF UNIX}{$IFDEF UseCThreads}
292- cthreads,
293- cmem, // the c memory manager is on some systems much faster for multi-threading
294- {$ENDIF}{$ENDIF}
295- heaptrc,
286+ {$IFDEF UNIX}{$IFDEF UseCThreads}
287+ cthreads,
288+ cmem, // the c memory manager is on some systems much faster for multi-threading
289+ {$ENDIF}{$ENDIF}
290+ heaptrc,
296291```
297292
298293## SMP(対称型マルチプロセッシング)のサポート
299294
300-難しく考える必要はありません。このページで紹介されているマルチスレッドのやり方でアプリケーションを適切な動作をさせればいいのです。SMP は常に有効になっていますから。
295+難しく考える必要はありません。このページで紹介されているマルチスレッドのやり方でアプリケーションを適切な動作をさせればいいのです。SMP は常に有効になっていますから。
301296
302297## Lazarus でのマルチスレッドアプリケーションのデバッグ
303298
304-LazarusでのデバグにはGDBが必要です。Lazarusのデバグ環境はどんどんフル装備かつ安定になっています。しかしながら Linux の一部のディストリビューションには問題をかかえたものもあります。 (* 訳注: MacOSX 10.8以降の場合、Xcode から GDB が失われているため、ユーザが自力で GDB をインストールし、デバグのための特権を与える必要があります *)
299+LazarusでのデバグにはGDBが必要です。Lazarusのデバグ環境はどんどんフル装備かつ安定になっています。しかしながら Linux の一部のディストリビューションには問題をかかえたものもあります。 (* 訳注: MacOSX 10.8以降の場合、Xcode から GDB が失われているため、ユーザが自力で GDB をインストールし、デバグのための特権を与える必要があります *)
305300
306301### デバッグ出力
307302
308-単一スレッドアプリケーションでは、単純にコンソールでもターミナルでも好きなものに出力すれば、出力した順に行が並びます。マルチスレッドアプリケーションでは事態はもっと複雑です。スレッドAがスレッドBより先に一行出力したからといって、その順に書き込まれるとは限りません。あるスレッドが自分の出力を書いているときに、たまたま他のスレッドが行を書き込んでいるかも知れないのです。Linux では(多分)適切なDebugLn() 出力を得られるでしょう。Win32 ではDegubLn() をメインスレッドの外で使っていることによる例外が発生するかもしれません(おそらく DiskFull 例外)。頭痛を軽減するには、下記の DebugLnThreadLog() をお使いください。
309-
310-The LCLProc ユニットには、各スレッドが自身のログファイルを書くための手続き/関数がいくつかあります:
303+単一スレッドアプリケーションでは、単純にコンソールでもターミナルでも好きなものに出力すれば、出力した順に行が並びます。マルチスレッドアプリケーションでは事態はもっと複雑です。スレッドAがスレッドBより先に一行出力したからといって、その順に書き込まれるとは限りません。あるスレッドが自分の出力を書いているときに、たまたま他のスレッドが行を書き込んでいるかも知れないのです。Linux では(多分)適切なDebugLn() 出力を得られるでしょう。Win32 ではDegubLn() をメインスレッドの外で使っていることによる例外が発生するかもしれません(おそらく DiskFull 例外)。頭痛を軽減するには、下記の DebugLnThreadLog() をお使いください。
311304
312-``` pascal
313- procedure DbgOutThreadLog(const Msg: string); overload;
314- procedure DebuglnThreadLog(const Msg: string); overload;
315- procedure DebuglnThreadLog(Args: array of const); overload;
316- procedure DebuglnThreadLog; overload;
305+The LCLProc ユニットには、各スレッドが自身のログファイルを書くための手続き/関数がいくつかあります:
317306```
318-
319-例: *writeln('Some text ',123);* の代わりに、これを使います
320-
321-```text
322- DebuglnThreadLog(['Some text ',123]);
307+procedure DbgOutThreadLog(const Msg: string); overload;
308+ procedure DebuglnThreadLog(const Msg: string); overload;
309+ procedure DebuglnThreadLog(Args: array of const); overload;
310+ procedure DebuglnThreadLog; overload;
323311```
324312
325-こうすると、 'Some text 123' という行が **Log\<PID\>.txt** に追加されます。ここで \<PID\> はそのスレッドのプロセス ID です。
313+例: _writeln('Some text ',123);_ の代わりに、これを使います
314+```
315+DebuglnThreadLog(['Some text ',123]);
316+```
326317
327-実行する前毎に、ログファイルを削除するとよいでしょう:
318+こうすると、 'Some text 123' という行が **Log <PID>.txt** に追加されます。ここで <PID> はそのスレッドのプロセス ID です。
328319
329-```text
330- rm -f Log* && ./project1
320+実行する前毎に、ログファイルを削除するとよいでしょう:
331321```
322+rm -f Log* && ./project1
323+```
332324
333325### Linux
334326
335-Linux でマルチスレッドアプリケーションをデバグする場合、一つの大きな問題があります: Xサーバの Desktop Manager がハングするかもしれません。例えば、デバグ中のアプリケーションがマウスやキーボードを捕獲し、そこで gdb がアプリケーションを一時停止させ、Xサーバがアプリケーションを待ってしまうとこうなります。こうなってしまったら、他のコンピュータからログインするか、CTRL+ALT+F3 でそのセッションから抜け出すかして、gdb を kill してください。あるいは、ウィンドウマネジャを再起動することもできます: 次のように入力してください sudo /etc/init.d/gdm restart。これで Desktop Manager が再起動し、デスクトップに戻れます。
336-
337-この問題は、gdb がデバグ中のプログラムのどこで実行を止めるかに依存するので、ちょっとしたトリックが有効な場合があります: Ubuntu x64 では、プロジェクトオプション → コンパイラオプション → リンク → デバッグ:外部の gdb デバッグシンボルファイルを使用 (-Xg) をチェックします。
327+Linux でマルチスレッドアプリケーションをデバグする場合、一つの大きな問題があります: Xサーバの Desktop Manager がハングするかもしれません。例えば、デバグ中のアプリケーションがマウスやキーボードを捕獲し、そこで gdb がアプリケーションを一時停止させ、Xサーバがアプリケーションを待ってしまうとこうなります。こうなってしまったら、他のコンピュータからログインするか、CTRL+ALT+F3 でそのセッションから抜け出すかして、gdb を kill してください。あるいは、ウィンドウマネジャを再起動することもできます: 次のように入力してください sudo /etc/init.d/gdm restart。これで Desktop Manager が再起動し、デスクトップに戻れます。
338328
339-別のXデスクトップを開く手もあります。IDEとgdbを動かすデスクトップと、アプリケーションを動かすデスクトップを分けるのです。そうすれば固まるのはテスト用のデスクトップだけです。Xの別インスタンスを作るには:
329+この問題は、gdb がデバグ中のプログラムのどこで実行を止めるかに依存するので、ちょっとしたトリックが有効な場合があります: Ubuntu x64 では、プロジェクトオプション → コンパイラオプション → リンク → デバッグ:外部の gdb デバッグシンボルファイルを使用 (-Xg) をチェックします。
340330
341-```text
342- X :1 &
331+別のXデスクトップを開く手もあります。IDEとgdbを動かすデスクトップと、アプリケーションを動かすデスクトップを分けるのです。そうすれば固まるのはテスト用のデスクトップだけです。Xの別インスタンスを作るには:
343332```
344-
345-とします。元のデスクトップに戻るには CTRL+ALT+F7を、再び新しいデスクトップに行くには CTRL+ALT+F8 を使います (このキーコンビネーションがうまく行かなかったら、CTRL+ALT+F2 を試してください... [Slackware](http://www.slackware.com)で有効でした)。
333+X :1 &
334+```
346335
347-これで、X上のデスクトップセッションを始められます:
336+とします。元のデスクトップに戻るには CTRL+ALT+F7を、再び新しいデスクトップに行くには CTRL+ALT+F8 を使います (このキーコンビネーションがうまく行かなかったら、CTRL+ALT+F2 を試してください... [Slackware](<http://www.slackware.com>)で有効でした)。
348337
349-```text
350- gnome-session --display=:1 &
338+これで、X上のデスクトップセッションを始められます:
351339```
340+gnome-session --display=:1 &
341+```
352342
353-そしてLazarusでプロジェクトの run parameters dialogで"Use display"をチェックして、:1を入力します。
343+そしてLazarusでプロジェクトの run parameters dialogで"Use display"をチェックして、:1を入力します。
354344
355-これでアプリケーションは2番目のXサーバーで動作するようになり、最初のXサーバーでデバッグが可能になりました。
345+これでアプリケーションは2番目のXサーバーで動作するようになり、最初のXサーバーでデバッグが可能になりました。
356346
357-この方法は、FPC2.0 と Lazarus 0.9.10 で、OS は Windows と Linux でテストしました。
347+この方法は、FPC2.0 と Lazarus 0.9.10 で、OS は Windows と Linux でテストしました。
358348
359-------------------------------------------------------------------------
349+
360350
361-新しい X セッションを開始する代わりに、 [Xnest](http://en.wikipedia.org/wiki/Xnest) を用いることもできます。Xnest は X セッションをウィンドウで開くものです。これを用いると、スレッドをデバッグする間も X サーバがロックしません。また、複数のターミナルを行ったり来たりしながらデバグするよりも楽です。
351+* * *
362352
363-Xnestを起動するには、次のようなコマンドをタイプします:
353+新しい X セッションを開始する代わりに、 [Xnest](<http://en.wikipedia.org/wiki/Xnest>) を用いることもできます。Xnest は X セッションをウィンドウで開くものです。これを用いると、スレッドをデバッグする間も X サーバがロックしません。また、複数のターミナルを行ったり来たりしながらデバグするよりも楽です。
364354
365-```text
366- Xnest :1 -ac
355+Xnestを起動するには、次のようなコマンドをタイプします:
367356```
357+Xnest :1 -ac
358+```
368359
369-一つの X セッションが :1 で開き、アクセス制御がディセーブルされます。
360+一つの X セッションが :1 で開き、アクセス制御がディセーブルされます。
370361
371362## Lazarus ウィジェットセットインターフェース
372363
373-win32、gtk、carbon インターフェースはマルチスレッドをサポートしています。それは TThread、クリティカルセクション、同期動作のことを指しています。しかしながら、これらはスレッドセーフではないのです。つまり、ある瞬間に同時に LCL にアクセスできるスレッドは 1 つに限られているということです。また、メインスレッドが他のスレッドに対して待機するようなことは通常はなく、メインスレッドだけが LCL へのアクセスを許可されているのです。TControl、アプリケーション、LCL ウィジェットの各ハンドルを使って動作させられるのはメインスレッドだけなのです。その一方で、LCL にはスレッドセーフな関数もいくつかあります。例えば、FileUtil ユニット内のほとんどの関数はスレッドセーフです。
364+win32、gtk、carbon インターフェースはマルチスレッドをサポートしています。それは TThread、クリティカルセクション、同期動作のことを指しています。しかしながら、これらはスレッドセーフではないのです。つまり、ある瞬間に同時に LCL にアクセスできるスレッドは 1 つに限られているということです。また、メインスレッドが他のスレッドに対して待機するようなことは通常はなく、メインスレッドだけが LCL へのアクセスを許可されているのです。TControl、アプリケーション、LCL ウィジェットの各ハンドルを使って動作させられるのはメインスレッドだけなのです。その一方で、LCL にはスレッドセーフな関数もいくつかあります。例えば、FileUtil ユニット内のほとんどの関数はスレッドセーフです。
374365
375366### スレッド間のやり取りに SendMessage/PostMessage を使用する
376367
377-1 つのアプリケーションにおいて LCL API を呼び出すのはただ 1 つであるべきで、通常はメインスレッドがそれを担っています。いくつかの間接的手段を通すことで、他のスレッドでも LCL の使用が可能になります。SendMessage や PostMessage の使用もそのような手段の一つといえるでしょう。LCLIntf.SendMessage と LCLIntf.PostMessage は、メッセージをアプリケーションのメッセージプールに送り、ウィンドウを直接制御するものです。
378-
379-これらのルーチンのドキュメントもお読みください。
368+1 つのアプリケーションにおいて LCL API を呼び出すのはただ 1 つであるべきで、通常はメインスレッドがそれを担っています。いくつかの間接的手段を通すことで、他のスレッドでも LCL の使用が可能になります。SendMessage や PostMessage の使用もそのような手段の一つといえるでしょう。LCLIntf.SendMessage と LCLIntf.PostMessage は、メッセージをアプリケーションのメッセージプールに送り、ウィンドウを直接制御するものです。
380369
381-- [[doc:lcl/lclintf/sendmessage.html|SendMessage]]
382-- [[doc:lcl/lclintf/postmessage.html|PostMessage]]
370+これらのルーチンのドキュメントもお読みください。
383371
384-SendMessage と PostMessage の違いは、呼び出した側への制御の復帰方法です。SendMessage ではメッセージを送った先のウィンドウの処理が完了するまで制御が戻りませんが、PostMessage では即座に制御が戻ります。
372+ * [SendMessage](<http://lazarus-ccr.sourceforge.net/docs/lcl/lclintf/sendmessage.html> "doc:lcl/lclintf/sendmessage.html")
373+ * [PostMessage](<http://lazarus-ccr.sourceforge.net/docs/lcl/lclintf/postmessage.html> "doc:lcl/lclintf/postmessage.html")
385374
386-以下は、別のスレッドよりメインスレッドへ LCL コントロールに表示させるテキストを送る方法の例です。
375+SendMessage と PostMessage の違いは、呼び出した側への制御の復帰方法です。SendMessage ではメッセージを送った先のウィンドウの処理が完了するまで制御が戻りませんが、PostMessage では即座に制御が戻ります。
387376
388-``` pascal
377+以下は、別のスレッドよりメインスレッドへ LCL コントロールに表示させるテキストを送る方法の例です。
378+```
389379const
390- WM_GOT_ERROR = LM_USER + 2004;
391- WM_VERBOSE = LM_USER + 2005;
392-
393-procedure VerboseLog(Msg: string);
394-var
395- PError: PChar;
396-begin
397- if MessageHandler = 0 then Exit;
398- PError := StrAlloc(Length(Msg)+1);
399- StrCopy(PError, PChar(Msg));
400- PostMessage(formConsole.Handle, WM_VERBOSE, Integer(PError), 0);
401-end;
380+ WM_GOT_ERROR = LM_USER + 2004;
381+ WM_VERBOSE = LM_USER + 2005;
382+
383+ procedure VerboseLog(Msg: string);
384+ var
385+ PError: PChar;
386+ begin
387+ if MessageHandler = 0 then Exit;
388+ PError := StrAlloc(Length(Msg)+1);
389+ StrCopy(PError, PChar(Msg));
390+ PostMessage(formConsole.Handle, WM_VERBOSE, Integer(PError), 0);
391+ end;
402392```
403393
404-こちらは、受け取ったメッセージの処理の例です。
405-
406-``` pascal
394+こちらは、受け取ったメッセージの処理の例です。
395+```
407396const
408- WM_GOT_ERROR = LM_USER + 2004;
409- WM_VERBOSE = LM_USER + 2005;
410-
411-type
412- { TformConsole }
413-
414- TformConsole = class(TForm)
415- DebugList: TListView;
416- // ...
417- private
418- procedure HandleDebug(var Msg: TLMessage); message WM_VERBOSE;
419- end;
420-
421-var
422- formConsole: TformConsole;
423-
424-implementation
425-
426-....
427-
428-{ TformConsole }
429-
430-procedure TformConsole.HandleDebug(var Msg: TLMessage);
431-var
432- Item: TListItem;
433- MsgStr: PChar;
434- MsgPasStr: string;
435-begin
436- MsgStr := PChar(Msg.wparam);
437- MsgPasStr := StrPas(MsgStr);
438- Item := DebugList.Items.Add;
439- Item.Caption := TimeToStr(SysUtils.Now);
440- Item.SubItems.Add(MsgPasStr);
441- Item.MakeVisible(False);
442- //f/TrayControl.SetError(MsgPasStr);
443-end;
444-
445-end.
397+ WM_GOT_ERROR = LM_USER + 2004;
398+ WM_VERBOSE = LM_USER + 2005;
399+
400+ type
401+ { TformConsole }
402+
403+ TformConsole = class(TForm)
404+ DebugList: TListView;
405+ // ...
406+ private
407+ procedure HandleDebug(var Msg: TLMessage); message WM_VERBOSE;
408+ end;
409+
410+ var
411+ formConsole: TformConsole;
412+
413+ implementation
414+
415+ ....
416+
417+ { TformConsole }
418+
419+ procedure TformConsole.HandleDebug(var Msg: TLMessage);
420+ var
421+ Item: TListItem;
422+ MsgStr: PChar;
423+ MsgPasStr: string;
424+ begin
425+ MsgStr := PChar(Msg.wparam);
426+ MsgPasStr := StrPas(MsgStr);
427+ Item := DebugList.Items.Add;
428+ Item.Caption := TimeToStr(SysUtils.Now);
429+ Item.SubItems.Add(MsgPasStr);
430+ Item.MakeVisible(False);
431+ //f/TrayControl.SetError(MsgPasStr);
432+ end;
433+
434+ end.
446435```
447436
448437## クリティカルセクション
449438
450-*クリティカルセクション*はオブジェクトの一つで、ある部分のコードがある時点では 1 つのスレッドだけで実行されること(排他的に実行されること)を保証するものです。クリティカルセクションは使用前に作成/初期化し、使用後は解放する必要があります。
451-
452-以下はクリティカルセクションの一般的な使い方です。
439+_クリティカルセクション_ はオブジェクトの一つで、ある部分のコードがある時点では 1 つのスレッドだけで実行されること(排他的に実行されること)を保証するものです。クリティカルセクションは使用前に作成/初期化し、使用後は解放する必要があります。
453440
454-セクションを宣言します(すべてのスレッドからアクセスできるようにグローバルにします)。
441+以下はクリティカルセクションの一般的な使い方です。
455442
456-```text
457- MyCriticalSection: TRTLCriticalSection;
443+セクションを宣言します(すべてのスレッドからアクセスできるようにグローバルにします)。
458444```
459-
460-セクションを作成します。
461-
462-```text
463- InitializeCriticalSection(MyCriticalSection);
445+MyCriticalSection: TRTLCriticalSection;
464446```
465447
466-スレッドを実行します。排他的な処理は以下のようにします。
448+セクションを作成します。
449+```
450+InitializeCriticalSection(MyCriticalSection);
451+```
467452
468-``` pascal
453+スレッドを実行します。排他的な処理は以下のようにします。
454+```
469455EnterCriticalSection(MyCriticalSection);
470-try
471- // 変数へのアクセス、ファイルの書き出し、ネットワークパケットの送信、など
472-finally
473- LeaveCriticalSection(MyCriticalSection);
474-end;
456+ try
457+ // 変数へのアクセス、ファイルの書き出し、ネットワークパケットの送信、など
458+ finally
459+ LeaveCriticalSection(MyCriticalSection);
460+ end;
475461```
476462
477-すべてのスレッドが停止した後、セクションを解放します。
478-
479-```text
480- DeleteCriticalSection(MyCriticalSection);
463+すべてのスレッドが停止した後、セクションを解放します。
481464```
465+DeleteCriticalSection(MyCriticalSection);
466+```
482467
483-別の方法として、TCriticalSection オブジェクトも使えます。オプジェクトを作成するとセクションの作成と初期化を、Enter メソッドを実行すると EnterCriticalSection と同様の動作を、Leave メソッドを実行すると LeaveCriticalSection と同様の動作を、オブジェクトを破棄するとセクションの削除を行います。
468+別の方法として、TCriticalSection オブジェクトも使えます。オプジェクトを作成するとセクションの作成と初期化を、Enter メソッドを実行すると EnterCriticalSection と同様の動作を、Leave メソッドを実行すると LeaveCriticalSection と同様の動作を、オブジェクトを破棄するとセクションの削除を行います。
484469
485-例:5 つのスレッドが 1 つのカウンターに対して増加処理を行います。\
486-lazarus/examples/multithreading/criticalsectionexample1.lpi
470+例:5 つのスレッドが 1 つのカウンターに対して増加処理を行います。
471+lazarus/examples/multithreading/criticalsectionexample1.lpi
487472
488-**注意:**上述の 4 つの手続きは RTL(FPC のランタイム)と LCL に一組ずつ存在します。LCL のは LCLIntf と LCLType ユニットで定義されています(RTL のは System 系ユニット内)。どちらの組も同様に用いることができます。アプリケーションの中で混用することもできますが、RTL の関数/手続きには RTL のクリティカルセクションを、LCL の関数/手続きには LCL のクリティカルセクションを使わなければなりません。
473+**注意:** 上述の 4 つの手続きは RTL(FPC のランタイム)と LCL に一組ずつ存在します。LCL のは LCLIntf と LCLType ユニットで定義されています(RTL のは System 系ユニット内)。どちらの組も同様に用いることができます。アプリケーションの中で混用することもできますが、RTL の関数/手続きには RTL のクリティカルセクションを、LCL の関数/手続きには LCL のクリティカルセクションを使わなければなりません。
489474
490475### 変数の共用
491476
492-複数のスレッドが 1 つの変数を共用する場合、読み出すだけなら何も問題はありません。単に読めばいいのです。しかし、1 つまたは複数のスレッドがその値を変更しようとするなら、複数のスレッドがその変数に同時にアクセスしないように注意しなければなりません。
477+複数のスレッドが 1 つの変数を共用する場合、読み出すだけなら何も問題はありません。単に読めばいいのです。しかし、1 つまたは複数のスレッドがその値を変更しようとするなら、複数のスレッドがその変数に同時にアクセスしないように注意しなければなりません。
493478
494-例:5 つのスレッドが 1 つのカウンターに対して増加処理を行います。\
495-lazarus/examples/multithreading/criticalsectionexample1.lpi
479+例:5 つのスレッドが 1 つのカウンターに対して増加処理を行います。
480+lazarus/examples/multithreading/criticalsectionexample1.lpi
496481
497482## 別のスレッドの処理を待つ方法
498483
499-スレッド A が別のスレッド B の結果を利用するなら、A は B の処理が終了するまで待つ必要があります。
500-
501-**重要:**メインスレッドは決して他のスレッドの処理を待ってはいけません。その代わりに上述の Synchronize を用います。
484+スレッド A が別のスレッド B の結果を利用するなら、A は B の処理が終了するまで待つ必要があります。
502485
503-一例:lazarus/examples/multithreading/waitforexample1.lpi
486+**重要:** メインスレッドは決して他のスレッドの処理を待ってはいけません。その代わりに上述の Synchronize を用います。
504487
505-``` pascal
488+一例:lazarus/examples/multithreading/waitforexample1.lpi
489+```
506490{ TThreadA }
507-
508-procedure TThreadA.Execute;
509-begin
510- Form1.ThreadB:=TThreadB.Create(false);
511- // イベント作成
512- WaitForB:=RTLEventCreate;
513- while not Application.Terminated do begin
514- // B が A を起動させるまで待機し続けます
515- RtlEventWaitFor(WaitForB);
516- writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
517- end;
518-end;
519-
520-{ TThreadB }
521-
522-procedure TThreadB.Execute;
523-var
524- i: Integer;
525-begin
526- Counter:=0;
527- while not Application.Terminated do begin
528- // B 処理中 ...
529- Sleep(1500);
530- inc(Counter);
531- // A 起動
532- RtlEventSetEvent(Form1.ThreadA.WaitForB);
533- end;
534-end;
491+
492+ procedure TThreadA.Execute;
493+ begin
494+ Form1.ThreadB:=TThreadB.Create(false);
495+ // イベント作成
496+ WaitForB:=RTLEventCreate;
497+ while not Application.Terminated do begin
498+ // B が A を起動させるまで待機し続けます
499+ RtlEventWaitFor(WaitForB);
500+ writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
501+ end;
502+ end;
503+
504+ { TThreadB }
505+
506+ procedure TThreadB.Execute;
507+ var
508+ i: Integer;
509+ begin
510+ Counter:=0;
511+ while not Application.Terminated do begin
512+ // B 処理中 ...
513+ Sleep(1500);
514+ inc(Counter);
515+ // A 起動
516+ RtlEventSetEvent(Form1.ThreadA.WaitForB);
517+ end;
518+ end;
535519```
536520
537-> **Note:** RtlEventWaitFor の前に RtlEventSetEvent を呼ぶことはできますが、その場合 RtlEventWaitFor は即座に制御を返します。終了フラグをクリアしたいのでしたら RTLeventResetEvent を使用してください。
521+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** RtlEventWaitFor の前に RtlEventSetEvent を呼ぶことはできますが、その場合 RtlEventWaitFor は即座に制御を返します。終了フラグをクリアしたいのでしたら RTLeventResetEvent を使用してください。
538522
539523## Fork
540524
541-マルチスレッドアプリケーションにおける fork に関して、次のことに留意してください。fork (あるいはfpFork)を呼び出す前に生成し動作させたあらゆるスレッドは、子プロセスで走っているのではありません。fork() の man ページで述べられているように、fork を呼び出す前に走っているスレッドの状態は未定義になります。
525+マルチスレッドアプリケーションにおける fork に関して、次のことに留意してください。fork (あるいはfpFork)を呼び出す**前** に生成し動作させたあらゆるスレッドは、子プロセスで走っているのでは**ありません** 。fork() の man ページで述べられているように、fork を呼び出す前に走っているスレッドの状態は未定義になります。
542526
543-従って、fork を呼び出す前に初期化されたスレッドは、intialization 節を含め、動作しません。
527+従って、fork を呼び出す前に初期化されたスレッドは、intialization 節を含め、動作しません。
544528
545529## 並列手続き/ループ
546530
547-マルチスレッドの内の特殊なもので、単一の手続きを並列実行するものです。[[Parallel procedures/ja|並列手続きをご]]覧ください。
531+マルチスレッドの内の特殊なもので、単一の手続きを並列実行するものです。[[Parallel procedures/ja|並列手続き]]をご覧ください。
548532
549533## 分散コンピューティング
550534
551-マルチスレッドを更に踏み込んだもので、複数のマシンでスレッドを稼動させます。
535+マルチスレッドを更に踏み込んだもので、複数のマシンでスレッドを稼動させます。
552536
553-- synapse、lnet、indy のような TCP スイートのいずれかを通信に使用します。これらは柔軟性に富み、クライアントとサーバのアプリケーションの接続の自由度上げたい場合よく用いられます。
554-- [[MPICH]] のようなメッセージパッシングライブラリを使用します。MPICH はコンピュータクラスタ上で高性能計算をするのに使われています。
537+ * synapse、lnet、indy のような TCP スイートのいずれかを通信に使用します。これらは柔軟性に富み、クライアントとサーバのアプリケーションの接続の自由度上げたい場合よく用いられます。
538+ * [[MPICH]] のようなメッセージパッシングライブラリを使用します。MPICH はコンピュータクラスタ上で高性能計算をするのに使われています。
555539
540+
541+
556542## 外部スレッド
557543
558-Free Pascal のスレッディングシステムを正しく動作させるには、新たに生成された FPC スレッドを初期化する必要があります(より正確には、スレッド毎に例外・I/O・スレッド変数 threadvar システムを初期化し、スレッド変数とヒープを動作可能にしなければなりません)。この過程は BeginThread を(あるいは間接的に TThread クラスを)用いると完全に自動的に行われます。しかし BeginThread を通さす生成されたスレッド(外部スレッド)を利用する場合、追加の作業が(いまのところ)必要となります。外部スレッドには外部の C ライブラリ (.DLL/.so) が生成したものも含みます。
544+Free Pascal のスレッディングシステムを正しく動作させるには、新たに生成された FPC スレッドを初期化する必要があります(より正確には、スレッド毎に例外・I/O・スレッド変数 threadvar システムを初期化し、スレッド変数とヒープを動作可能にしなければなりません)。この過程は BeginThread を(あるいは間接的に TThread クラスを)用いると完全に自動的に行われます。しかし BeginThread を通さす生成されたスレッド(外部スレッド)を利用する場合、追加の作業が(いまのところ)必要となります。外部スレッドには外部の C ライブラリ (.DLL/.so) が生成したものも含みます。
559545
560-外部スレッドを用いる際に考慮すべき点です(将来のコンパイラのヴァージョンでは不要になるかもしれません):
546+外部スレッドを用いる際に考慮すべき点です(将来のコンパイラのヴァージョンでは不要になるかもしれません):
561547
562-- 外部スレッドを一切使わない - FPC スレッドを使う。スレッドの生成を完全に制御できるなら、BeginThread を使って自分でスレッドを生成します。
548+ * 外部スレッドを一切使わない - FPC スレッドを使う。スレッドの生成を完全に制御できるなら、BeginThread を使って自分でスレッドを生成します。
563549
564-- 呼出規約が一致しない場合(生成しようとするスレッド関数が cdecl 呼出規約を必要としているのに、BeginThread が Pascal 呼出規約を必要としているような場合)、レコード型を一つ作り、オリジナルのスレッド関数をその中に入れて、Pascal スレッド関数側から次のように呼びます:
550+ * 呼出規約が一致しない場合(生成しようとするスレッド関数が cdecl 呼出規約を必要としているのに、BeginThread が Pascal 呼出規約を必要としているような場合)、レコード型を一つ作り、オリジナルのスレッド関数をその中に入れて、Pascal スレッド関数側から次のように呼びます:
565551
566-``` pascal
552+```
567553type
568- TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;
554+ TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;
555+
556+ PCdeclThreadFuncData = ^TCdeclThreadFuncData;
557+ TCdeclThreadFuncData = record
558+ Func: TCdeclThreadFunc; //cdecl 関数
559+ Data: Pointer; //ユーザデータ
560+ end;
561+
562+ // Pascal スレッドは cdecl 関数で呼ばれます
563+ function C2P_Translator(FuncData: pointer) : ptrint;
564+ var
565+ ThreadData: TCdeclThreadFuncData;
566+ begin
567+ ThreadData := PCdeclThreadFuncData(FuncData)^;
568+ Result := ptrint(ThreadData.Func(ThreadData.Data));
569+ end;
570+
571+ procedure CreatePascalThread;
572+ var
573+ ThreadData: PCdeclThreadFuncData;
574+ begin
575+ New(ThreadData);
576+ // cdecl スレッド関数でなければなりません
577+ ThreadData^.Func := func;
578+ ThreadData^.Data := user_data;
579+ // Pascal スレッドを作成
580+ BeginThread(@C2P_Translator, ThreadData );
581+ end;
582+```
569583
570- PCdeclThreadFuncData = ^TCdeclThreadFuncData;
571- TCdeclThreadFuncData = record
572- Func: TCdeclThreadFunc; //cdecl 関数
573- Data: Pointer; //ユーザデータ
574- end;
584+
575585
576-// Pascal スレッドは cdecl 関数で呼ばれます
577-function C2P_Translator(FuncData: pointer) : ptrint;
578-var
579- ThreadData: TCdeclThreadFuncData;
580-begin
581- ThreadData := PCdeclThreadFuncData(FuncData)^;
582- Result := ptrint(ThreadData.Func(ThreadData.Data));
583-end;
586+ * ダミーのスレッドを一つ生成して、FPC のスレッドシステムを初期化します。アプリケーションの中で Pascal スレッドを一つでも生成しないと、スレッドシステムは初期化されず、スレッド変数もヒープも正しく動作しません。
584587
585-procedure CreatePascalThread;
586-var
587- ThreadData: PCdeclThreadFuncData;
588-begin
589- New(ThreadData);
590- // cdecl スレッド関数でなければなりません
591- ThreadData^.Func := func;
592- ThreadData^.Data := user_data;
593- // Pascal スレッドを作成
594- BeginThread(@C2P_Translator, ThreadData );
595-end;
596588```
597-
598-- ダミーのスレッドを一つ生成して、FPC のスレッドシステムを初期化します。アプリケーションの中で Pascal スレッドを一つでも生成しないと、スレッドシステムは初期化されず、スレッド変数もヒープも正しく動作しません。
599-
600-``` pascal
601589type
602- tc = class(tthread)
603- procedure execute;override;
604- end;
605-
606- procedure tc.execute;
607- begin
608- end;
609-
610-{ メインプログラム }
611-begin
612- { スレッドの初期設定 }
613- with tc.create(false) do
614- begin
615- waitfor;
616- free;
617- end;
618- { ... コードが続く }
619-end.
590+ tc = class(tthread)
591+ procedure execute;override;
592+ end;
593+
594+ procedure tc.execute;
595+ begin
596+ end;
597+
598+ { メインプログラム }
599+ begin
600+ { スレッドの初期設定 }
601+ with tc.create(false) do
602+ begin
603+ waitfor;
604+ free;
605+ end;
606+ { ... コードが続く }
607+ end.
620608```
621609
622-(スレッドシステムの初期化が終わると、ランタイムライブラリはシステム変数 IsMultiThread を true にします。この変数はロックを行うのに FPC のルーチンのそこかしこで用いられます。この変数を手動で設定してはいけません。)
610+(スレッドシステムの初期化が終わると、ランタイムライブラリはシステム変数 IsMultiThread を true にします。この変数はロックを行うのに FPC のルーチンのそこかしこで用いられます。この変数を手動で設定してはいけません。)
623611
624-- なんらかの理由でこれが上手く行かなかったら、次のコードを外部スレッド関数の中で使ってみてください:
612+
625613
626-``` pascal
614+ * なんらかの理由でこれが上手く行かなかったら、次のコードを外部スレッド関数の中で使ってみてください:
615+
616+```
627617function ExternalThread(param: Pointer): LongInt; stdcall;
628-var
629- tm: TThreadManager;
630-begin
631- GetThreadManager(tm);
632- tm.AllocateThreadVars;
633- InitThread(1000000); // ここでスタックサイズの初期値を調節します
634-
635- { スレッド化されたなにかをここで実行する ... }
636-
637- Result:=0;
638-end;
618+ var
619+ tm: TThreadManager;
620+ begin
621+ GetThreadManager(tm);
622+ tm.AllocateThreadVars;
623+ InitThread(1000000); // ここでスタックサイズの初期値を調節します
624+
625+ { スレッド化されたなにかをここで実行する ... }
626+
627+ Result:=0;
628+ end;
639629```
640630
641-### 外部スレッドの確認
631+
642632
643-時として、外部スレッドを取り扱わねばならないのか、はっきり分からないことがあります(例えば、C ライブラリがコールバックを使用しているなど)。これは、以下のようにして解析することができます。
633+### 外部スレッドの確認
644634
645-1\. アプリケーションの現在のスレッド ID を調べます。
635+時として、外部スレッドを取り扱わねばならないのか、はっきり分からないことがあります(例えば、C ライブラリがコールバックを使用しているなど)。これは、以下のようにして解析することができます。
646636
647-``` pascal
637+1\. アプリケーションの現在のスレッド ID を調べます。
638+```
648639Win32: GetCurrentThreadID();
649-Darwin: GetThreadID();
650-Linux: TThreadID(pthread_self);
640+ Darwin: GetThreadID();
641+ Linux: TThreadID(pthread_self);
651642```
652643
653-2\. スレッド関数内部で再度現在のスレッド ID を調べ、ステップ 1 の結果と比較します。
644+2\. スレッド関数内部で再度現在のスレッド ID を調べ、ステップ 1 の結果と比較します。
654645
655646### タイムスライスの放棄
656647
657-ThreadSwitch 手続きを使ってください。
648+ThreadSwitch 手続きを使ってください。
658649
659-> **Note:** Windows の裏技であるような *Sleep(0)* は使わないでください。これはすべてのプラットフォーム上で動作するものではないのです。
650+![Light bulb](https://upload.wikimedia.org/wikipedia/commons/d/d8/Nuvola_apps_ktip.png) **Note:** Windows の裏技であるような _Sleep(0)_ は使わないでください。これはすべてのプラットフォーム上で動作するものではないのです。
660651
661-(訳注:タイムスライスは、1 回のスレッド処理に割り当てられる時間のことです。ThreadSwitch を実行すると残りの時間を放棄して、待機スレッドがあればそれらを行ってから、なければすぐに制御が戻ってきます。)
652+(訳注:タイムスライスは、1 回のスレッド処理に割り当てられる時間のことです。ThreadSwitch を実行すると残りの時間を放棄して、待機スレッドがあればそれらを行ってから、なければすぐに制御が戻ってきます。)
662653
663654## 関連項目
664655
665-- [[Streaming components/ja|コンポーネントをストリームする]]
666-- [[Manager Worker Threads System]]
667-- [[Example of multi-threaded application: array of threads/ja|マルチスレッドアプリケーションの例:スレッドの配列]]
668-- [[Parallel procedures/ja|並列手続き]]
669-- [[Main Loop Hooks/ja|メインループフック]]
670-- [[Asynchronous Calls/ja|非同期呼び出し]]
656+ * [[Streaming components/ja|コンポーネントをストリームする]]
657+ * [[Manager Worker Threads System]]
658+ * [[Example of multi-threaded application: array of threads/ja|マルチスレッドアプリケーションの例:スレッドの配列]]
659+ * [[Parallel procedures/ja|並列手続き]]
660+ * [[Main Loop Hooks/ja|メインループフック]]
661+ * [[Asynchronous Calls/ja|非同期呼び出し]]
671662
672663---
673-*From the Free Pascal wiki, page "Multithreaded Application Tutorial/ja", as it stood on 2020-02-20 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
664+*From the Free Pascal wiki, page "Multithreaded Application Tutorial/ja", as the Internet Archive kept it on 2024-07-14, last changed on 2020-02-21. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history