Sign in Join
Wiki copytemporary

Multithreaded Application Tutorial/ja: what changed

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

OldNewLine
11## 概要
22
33このページでは Free Pascal と Lazarus を使ってマルチスレッドアプリケーションを記述したりデバッグしたりする方法について述べます。マルチスレッドアプリケーションは、同時作業させるために複数のスレッドを作成しているものを指します。もし、読者がマルチスレッドに初めて触れるのなら、是非マルチスレッドが本当に必要かどうかを「マルチスレッドが必要ですか?」節でお確かめください。そうすれば頭痛の種を激減することができるでしょう。
44
55スレッドのうち 1 つは、メインスレッドと呼ばれるものです。メインスレッドはアプリケーションが起動する際、オペレーティングシステムによって生成されます。メインスレッドはユーザインターフェイス、コンポーネントを更新する唯一のスレッドです。メインスレッドは**かならず**ただ一つである必要があり、そうでないとアプリケーションは暴走します。
66
77マルチスレッドプログラミングの基本的な考え方は、ユーザがメインスレッドを通して作業する傍ら、もう一つのスレッドを用いてある種の処理をバックグラウンドで行えるようにアプリケーションを組むという点です。
88
99スレッドの他の使い方として、アプリケーションの応答速度を改善することが挙げられます。例えば、あなたがアプリケーションを作られたとして、ユーザがボタンを押してアプリケーションがすごく重たい処理を開始したとします。処理の間、スクリーンは凍りつき反応が無くなり、ユーザはアプリケーションが落ちたと思うでしょう。よくないことです。もし、その重たい処理を 2 つ目のスレッドの中で実行するようにすれば、アプリケーションの応答性は保たれ、まるでそんな処理は行っていないかのように振舞います。このような場合には、スレッドを開始する前にフォームの処理を開始するボタンを使用不可能にしておくと、ユーザがそれ以上の作業を要求することを無くすことができ、好都合です。
1010
1111また、サーバアプリケーションを作る際に、同時に大量のクライアントに応答しなければならない場合にも使えます。
1212
1313## マルチスレッドが必要ですか?
1414
1515もし読者がマルチスレッドにこれまで触れたことがなく、単に重い処理を実行するアプリケーションの応答性を改善したいだけなら、マルチスレッドはお探しのものとは違うかもしれません。マルチスレッドアプリケーションはしばしば複雑であり、デバッグは常に困難です。また、多くの場合マルチスレッドは必要のないものです。単一スレッドアプリケーションで十分です。時間のかかるタスクを細かな部分に分割することができるなら、マルチスレッドの代わりに **Application.ProcessMessages** を使うべきです。このメソッドを使用すると、LCL に待機中のメッセージをすべて処理させたのち制御が戻ってきます。分割したタスクの一部分を実行したら Application.ProcessMessages を呼び、ユーザがアプリケーションを終了させたりどこかをクリックしたりしたかを確認し、あるいは進行状況表示を再描画したりします。その後タスクの次の部分を実行し、また Application.ProcessMessages を呼びます。
1616
1717例:大きなファイルの読み込みと処理\
1818examples/multithreading/singlethreadingexample1.lpi
1919
2020マルチスレッドが必要なのは、次のような場合だけです。
2121
2222- 同期型のネットワーク通信のような、ブロッキングタイプの処理の場合(訳注:同期型の場合タイムアウトするまで処理が戻ってこない可能性がありますから、例えば別スレッドでキャンセル待ち処理をするような使い方が考えられます)。
2323- マルチプロセッサによる同時処理(SMP)の場合。
2424- API を呼び出さなければならないためにそれ以上細かく分割できないアルゴリズムやライブラリの呼び出し(訳注:分割できない長い処理を普通に行うとハングアップして固まったかのように見えてしまいますから、時間がかかる処理は別スレッドで行い描画やウィンドウの移動などの操作にはメインスレッドで反応するような使い方が考えられます)。
2525
2626マルチプロセッサによる同時処理で処理速度を上げるためにマルチスレッドを使いたいのであれば、プログラムが 1 つの CPU コアのリソースを 100% フルに使い切っているか確認してください(例えば、プログラムでファイルの書き込みなど入出力の処理が多く占めている場合、時間がかかる作業ではありますが CPU には負荷はかかっていません。このようなケースでマルチスレッドを使ってもプログラムは速くならないでしょう。)。また、最適化レベルが最大の 3 なっているかも確認してください。最適化レベルを 1 から 3 に切り替えたら、プログラムが約 5 倍速くなったことがありました。
2727
2828## マルチスレッドアプリケーションで必要となるユニット
2929
3030Windows では動作させるのに必要なユニットは特にありません。Linux、Mac OS X、FreeBSD では cthreads ユニットが必要で、プロジェクトユニット(プログラムソース、通常は .lpr ファイル)の一番最初に*必ず*置かなくてなりません!
3131
3232Lazarus アプリケーションコードでは以下のようになるでしょう。(訳注:実際に見れば分かりますが、cthreads は自動生成された時点で既に UseCThreads スイッチ付きで組み込まれています。)
3333
3434``` pascal
3535program MyMultiThreadedProgram;
3636{$mode objfpc}{$H+}
3737uses
3838{$ifdef unix}
3939 cthreads,
4040 cmem, // the c memory manager is on some systems much faster for multi-threading
4141{$endif}
4242 Interfaces, // this includes the LCL widgetset
4343 Forms
4444 { you can add units here },
4545```
4646
4747これを怠ると、起動時に以下のエラーが出ます。
4848
49-` This binary has no thread support compiled in.`\
50-` Recompile the application with a thread-driver in the program uses clause before other units using thread.`\
51-` 訳:このバイナリはスレッドをサポートしないでコンパイルされています。`\
52-` スレッドドライバをスレッドを使用されているユニットより前のプログラム uses 節に置き、アプリケーションをコンパイルし直してください。`
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 節に置き、アプリケーションをコンパイルし直してください。
54+```
5355
5456> **Note:** "mcount" が見つからないというようなリンカエラーが発生することがあります。これが発生するのは、マルチスレッドコードを含むユニットを使っていて、cthreads ユニットの追加もしくはスマートリンクの使用が必要な場合です。
5557
5658> **Note:** 手続き SYSTEM_NOTHREADERROR 内でエラー "Project raised exception class 'RunError(232)'" が発生することがあります。これが発生するのは、コード内でスレッドが必要としており、cthreads ユニットの追加が必要な場合です。
5759
5860## TThread クラス
5961
6062サンプルは「examples/multithreading/」にあります。
6163
6264マルチスレッドアプリケーションは TThread クラスを使用すると簡単に作成できます。このクラスを用いると追加のスレッド(メインスレッドと並列に実行する)を作ることができます。そのためには通常、Create コンストラクタと、Execute メソッドの 2 つのメソッドをオーバーライドしなければなりません。
6365
6466コンストラクタは、スレッドを準備する際に使います。必要となる変数ないしプロパティに初期値を設定します。基となる TThread のコンストラクタは Suspended という引数を必要とします。名前からもお分かりのように、Suspended が False の場合、スレッドは作成後直ちに実行されます。逆に Suspended が True の場合、スレッドは停止状態で作成されます。この場合、スレッドを実行するには Start メソッドを呼びます。
6567
6668> **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 に置き換えてください。
6769
6870FPC バージョン 2.0.1 以降の場合、TThread.Create は StackSize という暗黙のパラメータを持っています。必要であれば作成されたスレッドのデフォルトスタックサイズを変更することが可能です。例えばスレッド内で深い再帰呼び出しを行う際には重宝するでしょう。スタックサイズを指定しなかった場合は、OS による規定のスタックサイズが使用されます。
6971
7072スレッドで実行したいコードは、Execute メソッドをオーバーライドして、その中に書きます。
7173
7274TThread クラスは一つの重要なプロパティを持っています。それは、
7375
74-`Terminated : boolean;`
76+```text
77+Terminated : boolean;
78+```
7579
7680です。
7781
7882Terminated のデフォルト値は False です。通常の場合、スレッドはループを持っていますが、Terminated が True になったら、そのループから脱出しなければなりません。したがってループのサイクル毎に、Terminated が True になっていないかチェックする必要があります。もし True になっていたなら、速やかに必要な終了処理を行い、Execute メソッドを終了させなければなりません。ですから、Terminate メソッドを呼び出してもデフォルト状態では何も起きないことを、しっかり覚えておいてください。Execute メソッド自体が自分自身を終了するように明示的に実装しなければなりません。
7983
8084先に説明したように、スレッドはビジュアルコンポーネントを操作すべきではありません。なにかをユーザに表示するためにはメインスレッドで行います。
8185
8286これを行うために TThread には Synchronize というメソッドがあります。このメソッドは引数として、引数を持たないメソッドを 1 つとります。例えば、MyMethod というメソッドをスレッドから実行するには、Synchronize(@MyMethod) として呼び出します。この時スレッドの実行は一時的に停止し、メインスレッドから当該メソッドが実行され、その後スレッド実行が再開されます。
8387
8488Synchronize の正確な動作はプラットフォーム依存しますが、基本的には以下のようになります。
8589
8690- メッセージをメインメッセージキューに投げ、スレッドは休眠状態になります。
8791- メインスレッドでメッセージが処理され、MyMethod が呼ばれます。MyMethod は(デバイスや描画用の)コンテクスト無しで実行されます。つまり、MouseDown イベントや Paint イベント実行中に割り込んで実行されるのではなく、これらのイベントは後で実行されます。
8892- メインスレッドで MyMethod が実行された後、スレッドの休眠状態が解かれ、次のメッセージが処理されます。
8993- スレッドの処理が再開されます。
9094
9195TThread が持つもう一つの重要なプロパティに FreeOnTerminate があります。このプロパティを True にしておくと、スレッドの実行(Execute メソッド)が停止した後にスレッドオブジェクトは自動的に解放されます。これを行わない場合は、アプリケーション側で手動でオブジェクトを解放する必要があります。\
9296(訳注:以下の例では単に FreeOnTerminate = True しているだけなので、スレッドが終了した場合 MyThread 変数が示すアドレスに不正にアクセスできてしまいます。スレッド終了時に必ず呼ばれる TThread.OnTerminate で MyThread := nil するなどして適切な処置を行ってください。OnTerminate イベントも Synchronize を通して呼ばれています。)
9397
9498例:
9599
96100``` pascal
97101 Type
98102 TMyThread = class(TThread)
99103 private
100104 fStatusText : string;
101105 procedure ShowStatus;
102106 protected
103107 procedure Execute; override;
104108 public
105109 Constructor Create(CreateSuspended : boolean);
106110 end;
107111
108112 constructor TMyThread.Create(CreateSuspended : boolean);
109113 begin
110114 FreeOnTerminate := True;
111115 inherited Create(CreateSuspended);
112116 end;
113117
114118 procedure TMyThread.ShowStatus;
115119 // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
116120 begin
117121 Form1.Caption := fStatusText;
118122 end;
119123
120124 procedure TMyThread.Execute;
121125 var
122126 newStatus : string;
123127 begin
124128 fStatusText := 'TMyThread Starting...';
125129 Synchronize(@Showstatus);
126130 fStatusText := 'TMyThread Running...';
127131 while (not Terminated) and ([他のループ終了条件]) do
128132 begin
129133 ...
130134 [ここにスレッドループの本体コードを記述します]
131135 ...
132136 if NewStatus <> fStatusText then
133137 begin
134138 fStatusText := newStatus;
135139 Synchronize(@Showstatus);
136140 end;
137141 end;
138142 end;
139143```
140144
141145アプリケーション側では、
142146
143147``` pascal
144148 var
145149 MyThread : TMyThread;
146150 begin
147151 MyThread := TMyThread.Create(True); // このようにすると自動的に開始しません
148152 ...
149153 [ここにスレッドを実行する前に必要な初期化コードを記述します]
150154 ...
151155 MyThread.Start;
152156 end;
153157```
154158
155159もっと柔軟な制御を望むなら、スレッドに対しイベントを作成します。こうすることで、イベントに対してイベントハンドラで設定できるようになり、Synchronize されたメソッドは特定のフォームやクラスに縛られなくなります。以下が例です。
156160
157161``` pascal
158162 Type
159163 TShowStatusEvent = procedure(Status: String) of Object;
160164
161165 TMyThread = class(TThread)
162166 private
163167 fStatusText : string;
164168 FOnShowStatus: TShowStatusEvent;
165169 procedure ShowStatus;
166170 protected
167171 procedure Execute; override;
168172 public
169173 Constructor Create(CreateSuspended : boolean);
170174 property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
171175 end;
172176
173177 constructor TMyThread.Create(CreateSuspended : boolean);
174178 begin
175179 FreeOnTerminate := True;
176180 inherited Create(CreateSuspended);
177181 end;
178182
179183 procedure TMyThread.ShowStatus;
180184 // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
181185 begin
182186 if Assigned(FOnShowStatus) then
183187 begin
184188 FOnShowStatus(fStatusText);
185189 end;
186190 end;
187191
188192 procedure TMyThread.Execute;
189193 var
190194 newStatus : string;
191195 begin
192196 fStatusText := 'TMyThread Starting...';
193197 Synchronize(@Showstatus);
194198 fStatusText := 'TMyThread Running...';
195199 while (not Terminated) and ([他のループ終了条件]) do
196200 begin
197201 ...
198202 [ここにスレッドループの本体コードを記述します]
199203 ...
200204 if NewStatus <> fStatusText then
201205 begin
202206 fStatusText := newStatus;
203207 Synchronize(@Showstatus);
204208 end;
205209 end;
206210 end;
207211```
208212
209213アプリケーション側では、
210214
211215``` pascal
212216 Type
213217 TForm1 = class(TForm)
214218 Button1: TButton;
215219 Label1: TLabel;
216220 procedure FormCreate(Sender: TObject);
217221 procedure FormDestroy(Sender: TObject);
218222 private
219223 { private declarations }
220224 MyThread: TMyThread;
221225 procedure ShowStatus(Status: string);
222226 public
223227 { public declarations }
224228 end;
225229
226230 procedure TForm1.FormCreate(Sender: TObject);
227231 begin
228232 inherited;
229233 MyThread := TMyThread.Create(true);
230234 MyThread.OnShowStatus := @ShowStatus;
231235 end;
232236
233237 procedure TForm1.FormDestroy(Sender: TObject);
234238 begin
235239 MyThread.Terminate;
236240
237241 // FreeOnTerminate が true であれば、以下の記述は必要ありません。
238242 // MyThread.Free;
239243 inherited;
240244 end;
241245
242246 procedure TForm1.Button1Click(Sender: TObject);
243247 begin
244248 MyThread.Start;
245249 end;
246250
247251 procedure TForm1.ShowStatus(Status: string);
248252 begin
249253 Label1.Caption := Status;
250254 end;
251255```
252256
253257## 特に注意すべきこと
254258
255259### Windows におけるスタックチェック
256260
257261Windows で -Ct スイッチ(スタックチェック)を使うスレッドには頭の痛い問題があります。理由は定かではありませんが、デフォルトのスタックサイズを使う場合、スタックチェックが TTread.Create 内で問題を引き起こすことがあります。現在の運用上の回避策としては -Ct スイッチを使わないことです。一連の現象がメインスレッドで例外を引き起こすことは全くなく、新しく生成されたスレッド内で起こります。そのためスレッドが絶対開始できないように見えます。
258262
259263スレッド生成時に発生するその他の例外や、この問題をチェックする良いコードを示します。
260264
261265``` pascal
262266MyThread := TThread.Create(False);
263267if Assigned(MyThread.FatalException) then
264268 raise MyThread.FatalException;
265269```
266270
267271このコードは、スレッド生成時に発生するどんな例外でも、メインスレッドで生成させます。
268272
269273### パッケージでのマルチスレッド
270274
271275マルチスレッドを使用しているパッケージには、**-dUseCThreads** フラグをカスタム使用オプションに追加してください。パッケージエディタを開いて、オプション → 使用 → カスタム、そこへ *-dUseCThreads* を追加します。これによって、このパッケージを使用している IDE を含めたすべてのプロジェクトおよびパッケージでこのフラグが定義されます。IDE および IDE によって作成されたアプリケーションには、.lpr ファイル内に以下のコードが組み込まれています。(訳注:cmem は自動生成で組み込まれてはいません(Lazarus 1.0.12/1.2RC1 で確認)。)
272276
273277``` pascal
274278uses
275279 {$IFDEF UNIX}{$IFDEF UseCThreads}
276280 cthreads,
277281 cmem, // the c memory manager is on some systems much faster for multi-threading
278282 {$ENDIF}{$ENDIF}
279283```
280284
281285### Heaptrc
282286
283287*cmem* ユニットを使った上で -gh スイッチを使うことはできません。-gh スイッチは、(メモリーリークを調べるために)ヒープマネージャーを拡張する heaptrc ユニットを使用するものです。ですから(リークチェックしたいのであれば)、**cmem** ユニットを使ってから **その後に** **heaptrc** ユニットを置くようにする必要があります。(訳注:heaptrc は cmem を考慮するように作られているけど、cmem はそうではないからでしょう。)
284288
285289``` pascal
286290uses
287291 {$IFDEF UNIX}{$IFDEF UseCThreads}
288292 cthreads,
289293 cmem, // the c memory manager is on some systems much faster for multi-threading
290294 {$ENDIF}{$ENDIF}
291295 heaptrc,
292296```
293297
294298## SMP(対称型マルチプロセッシング)のサポート
295299
296300難しく考える必要はありません。このページで紹介されているマルチスレッドのやり方でアプリケーションを適切な動作をさせればいいのです。SMP は常に有効になっていますから。
297301
298302## Lazarus でのマルチスレッドアプリケーションのデバッグ
299303
300304LazarusでのデバグにはGDBが必要です。Lazarusのデバグ環境はどんどんフル装備かつ安定になっています。しかしながら Linux の一部のディストリビューションには問題をかかえたものもあります。 (* 訳注: MacOSX 10.8以降の場合、Xcode から GDB が失われているため、ユーザが自力で GDB をインストールし、デバグのための特権を与える必要があります *)
301305
302306### デバッグ出力
303307
304308単一スレッドアプリケーションでは、単純にコンソールでもターミナルでも好きなものに出力すれば、出力した順に行が並びます。マルチスレッドアプリケーションでは事態はもっと複雑です。スレッドAがスレッドBより先に一行出力したからといって、その順に書き込まれるとは限りません。あるスレッドが自分の出力を書いているときに、たまたま他のスレッドが行を書き込んでいるかも知れないのです。Linux では(多分)適切なDebugLn() 出力を得られるでしょう。Win32 ではDegubLn() をメインスレッドの外で使っていることによる例外が発生するかもしれません(おそらく DiskFull 例外)。頭痛を軽減するには、下記の DebugLnThreadLog() をお使いください。
305309
306310The LCLProc ユニットには、各スレッドが自身のログファイルを書くための手続き/関数がいくつかあります:
307311
308312``` pascal
309313 procedure DbgOutThreadLog(const Msg: string); overload;
310314 procedure DebuglnThreadLog(const Msg: string); overload;
311315 procedure DebuglnThreadLog(Args: array of const); overload;
312316 procedure DebuglnThreadLog; overload;
313317```
314318
315319例: *writeln('Some text ',123);* の代わりに、これを使います
316320
317-` DebuglnThreadLog(['Some text ',123]);`
321+```text
322+ DebuglnThreadLog(['Some text ',123]);
323+```
318324
319325こうすると、 'Some text 123' という行が **Log\<PID\>.txt** に追加されます。ここで \<PID\> はそのスレッドのプロセス ID です。
320326
321327実行する前毎に、ログファイルを削除するとよいでしょう:
322328
323-` rm -f Log* && ./project1`
329+```text
330+ rm -f Log* && ./project1
331+```
324332
325333### Linux
326334
327335Linux でマルチスレッドアプリケーションをデバグする場合、一つの大きな問題があります: Xサーバの Desktop Manager がハングするかもしれません。例えば、デバグ中のアプリケーションがマウスやキーボードを捕獲し、そこで gdb がアプリケーションを一時停止させ、Xサーバがアプリケーションを待ってしまうとこうなります。こうなってしまったら、他のコンピュータからログインするか、CTRL+ALT+F3 でそのセッションから抜け出すかして、gdb を kill してください。あるいは、ウィンドウマネジャを再起動することもできます: 次のように入力してください sudo /etc/init.d/gdm restart。これで Desktop Manager が再起動し、デスクトップに戻れます。
328336
329337この問題は、gdb がデバグ中のプログラムのどこで実行を止めるかに依存するので、ちょっとしたトリックが有効な場合があります: Ubuntu x64 では、プロジェクトオプション → コンパイラオプション → リンク → デバッグ:外部の gdb デバッグシンボルファイルを使用 (-Xg) をチェックします。
330338
331339別のXデスクトップを開く手もあります。IDEとgdbを動かすデスクトップと、アプリケーションを動かすデスクトップを分けるのです。そうすれば固まるのはテスト用のデスクトップだけです。Xの別インスタンスを作るには:
332340
333-` X :1 &`
341+```text
342+ X :1 &
343+```
334344
335345とします。元のデスクトップに戻るには CTRL+ALT+F7を、再び新しいデスクトップに行くには CTRL+ALT+F8 を使います (このキーコンビネーションがうまく行かなかったら、CTRL+ALT+F2 を試してください... [Slackware](http://www.slackware.com)で有効でした)。
336346
337347これで、X上のデスクトップセッションを始められます:
338348
339-` gnome-session --display=:1 &`
349+```text
350+ gnome-session --display=:1 &
351+```
340352
341353そしてLazarusでプロジェクトの run parameters dialogで"Use display"をチェックして、:1を入力します。
342354
343355これでアプリケーションは2番目のXサーバーで動作するようになり、最初のXサーバーでデバッグが可能になりました。
344356
345357この方法は、FPC2.0 と Lazarus 0.9.10 で、OS は Windows と Linux でテストしました。
346358
347359------------------------------------------------------------------------
348360
349361新しい X セッションを開始する代わりに、 [Xnest](http://en.wikipedia.org/wiki/Xnest) を用いることもできます。Xnest は X セッションをウィンドウで開くものです。これを用いると、スレッドをデバッグする間も X サーバがロックしません。また、複数のターミナルを行ったり来たりしながらデバグするよりも楽です。
350362
351363Xnestを起動するには、次のようなコマンドをタイプします:
352364
353-` Xnest :1 -ac`
365+```text
366+ Xnest :1 -ac
367+```
354368
355369一つの X セッションが :1 で開き、アクセス制御がディセーブルされます。
356370
357371## Lazarus ウィジェットセットインターフェース
358372
359373win32、gtk、carbon インターフェースはマルチスレッドをサポートしています。それは TThread、クリティカルセクション、同期動作のことを指しています。しかしながら、これらはスレッドセーフではないのです。つまり、ある瞬間に同時に LCL にアクセスできるスレッドは 1 つに限られているということです。また、メインスレッドが他のスレッドに対して待機するようなことは通常はなく、メインスレッドだけが LCL へのアクセスを許可されているのです。TControl、アプリケーション、LCL ウィジェットの各ハンドルを使って動作させられるのはメインスレッドだけなのです。その一方で、LCL にはスレッドセーフな関数もいくつかあります。例えば、FileUtil ユニット内のほとんどの関数はスレッドセーフです。
360374
361375### スレッド間のやり取りに SendMessage/PostMessage を使用する
362376
3633771 つのアプリケーションにおいて LCL API を呼び出すのはただ 1 つであるべきで、通常はメインスレッドがそれを担っています。いくつかの間接的手段を通すことで、他のスレッドでも LCL の使用が可能になります。SendMessage や PostMessage の使用もそのような手段の一つといえるでしょう。LCLIntf.SendMessage と LCLIntf.PostMessage は、メッセージをアプリケーションのメッセージプールに送り、ウィンドウを直接制御するものです。
364378
365379これらのルーチンのドキュメントもお読みください。
366380
367381- [[doc:lcl/lclintf/sendmessage.html|SendMessage]]
368382- [[doc:lcl/lclintf/postmessage.html|PostMessage]]
369383
370384SendMessage と PostMessage の違いは、呼び出した側への制御の復帰方法です。SendMessage ではメッセージを送った先のウィンドウの処理が完了するまで制御が戻りませんが、PostMessage では即座に制御が戻ります。
371385
372386以下は、別のスレッドよりメインスレッドへ LCL コントロールに表示させるテキストを送る方法の例です。
373387
374388``` pascal
375389const
376390 WM_GOT_ERROR = LM_USER + 2004;
377391 WM_VERBOSE = LM_USER + 2005;
378392
379393procedure VerboseLog(Msg: string);
380394var
381395 PError: PChar;
382396begin
383397 if MessageHandler = 0 then Exit;
384398 PError := StrAlloc(Length(Msg)+1);
385399 StrCopy(PError, PChar(Msg));
386400 PostMessage(formConsole.Handle, WM_VERBOSE, Integer(PError), 0);
387401end;
388402```
389403
390404こちらは、受け取ったメッセージの処理の例です。
391405
392406``` pascal
393407const
394408 WM_GOT_ERROR = LM_USER + 2004;
395409 WM_VERBOSE = LM_USER + 2005;
396410
397411type
398412 { TformConsole }
399413
400414 TformConsole = class(TForm)
401415 DebugList: TListView;
402416 // ...
403417 private
404418 procedure HandleDebug(var Msg: TLMessage); message WM_VERBOSE;
405419 end;
406420
407421var
408422 formConsole: TformConsole;
409423
410424implementation
411425
412426....
413427
414428{ TformConsole }
415429
416430procedure TformConsole.HandleDebug(var Msg: TLMessage);
417431var
418432 Item: TListItem;
419433 MsgStr: PChar;
420434 MsgPasStr: string;
421435begin
422436 MsgStr := PChar(Msg.wparam);
423437 MsgPasStr := StrPas(MsgStr);
424438 Item := DebugList.Items.Add;
425439 Item.Caption := TimeToStr(SysUtils.Now);
426440 Item.SubItems.Add(MsgPasStr);
427441 Item.MakeVisible(False);
428442 //f/TrayControl.SetError(MsgPasStr);
429443end;
430444
431445end.
432446```
433447
434448## クリティカルセクション
435449
436450*クリティカルセクション*はオブジェクトの一つで、ある部分のコードがある時点では 1 つのスレッドだけで実行されること(排他的に実行されること)を保証するものです。クリティカルセクションは使用前に作成/初期化し、使用後は解放する必要があります。
437451
438452以下はクリティカルセクションの一般的な使い方です。
439453
440454セクションを宣言します(すべてのスレッドからアクセスできるようにグローバルにします)。
441455
442-` MyCriticalSection: TRTLCriticalSection;`
456+```text
457+ MyCriticalSection: TRTLCriticalSection;
458+```
443459
444460セクションを作成します。
445461
446-` InitializeCriticalSection(MyCriticalSection);`
462+```text
463+ InitializeCriticalSection(MyCriticalSection);
464+```
447465
448466スレッドを実行します。排他的な処理は以下のようにします。
449467
450468``` pascal
451469EnterCriticalSection(MyCriticalSection);
452470try
453471 // 変数へのアクセス、ファイルの書き出し、ネットワークパケットの送信、など
454472finally
455473 LeaveCriticalSection(MyCriticalSection);
456474end;
457475```
458476
459477すべてのスレッドが停止した後、セクションを解放します。
460478
461-` DeleteCriticalSection(MyCriticalSection);`
479+```text
480+ DeleteCriticalSection(MyCriticalSection);
481+```
462482
463483別の方法として、TCriticalSection オブジェクトも使えます。オプジェクトを作成するとセクションの作成と初期化を、Enter メソッドを実行すると EnterCriticalSection と同様の動作を、Leave メソッドを実行すると LeaveCriticalSection と同様の動作を、オブジェクトを破棄するとセクションの削除を行います。
464484
465485例:5 つのスレッドが 1 つのカウンターに対して増加処理を行います。\
466486lazarus/examples/multithreading/criticalsectionexample1.lpi
467487
468488**注意:**上述の 4 つの手続きは RTL(FPC のランタイム)と LCL に一組ずつ存在します。LCL のは LCLIntf と LCLType ユニットで定義されています(RTL のは System 系ユニット内)。どちらの組も同様に用いることができます。アプリケーションの中で混用することもできますが、RTL の関数/手続きには RTL のクリティカルセクションを、LCL の関数/手続きには LCL のクリティカルセクションを使わなければなりません。
469489
470490### 変数の共用
471491
472492複数のスレッドが 1 つの変数を共用する場合、読み出すだけなら何も問題はありません。単に読めばいいのです。しかし、1 つまたは複数のスレッドがその値を変更しようとするなら、複数のスレッドがその変数に同時にアクセスしないように注意しなければなりません。
473493
474494例:5 つのスレッドが 1 つのカウンターに対して増加処理を行います。\
475495lazarus/examples/multithreading/criticalsectionexample1.lpi
476496
477497## 別のスレッドの処理を待つ方法
478498
479499スレッド A が別のスレッド B の結果を利用するなら、A は B の処理が終了するまで待つ必要があります。
480500
481501**重要:**メインスレッドは決して他のスレッドの処理を待ってはいけません。その代わりに上述の Synchronize を用います。
482502
483503一例:lazarus/examples/multithreading/waitforexample1.lpi
484504
485505``` pascal
486506{ TThreadA }
487507
488508procedure TThreadA.Execute;
489509begin
490510 Form1.ThreadB:=TThreadB.Create(false);
491511 // イベント作成
492512 WaitForB:=RTLEventCreate;
493513 while not Application.Terminated do begin
494514 // B が A を起動させるまで待機し続けます
495515 RtlEventWaitFor(WaitForB);
496516 writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
497517 end;
498518end;
499519
500520{ TThreadB }
501521
502522procedure TThreadB.Execute;
503523var
504524 i: Integer;
505525begin
506526 Counter:=0;
507527 while not Application.Terminated do begin
508528 // B 処理中 ...
509529 Sleep(1500);
510530 inc(Counter);
511531 // A 起動
512532 RtlEventSetEvent(Form1.ThreadA.WaitForB);
513533 end;
514534end;
515535```
516536
517537> **Note:** RtlEventWaitFor の前に RtlEventSetEvent を呼ぶことはできますが、その場合 RtlEventWaitFor は即座に制御を返します。終了フラグをクリアしたいのでしたら RTLeventResetEvent を使用してください。
518538
519539## Fork
520540
521541マルチスレッドアプリケーションにおける fork に関して、次のことに留意してください。fork (あるいはfpFork)を呼び出す前に生成し動作させたあらゆるスレッドは、子プロセスで走っているのではありません。fork() の man ページで述べられているように、fork を呼び出す前に走っているスレッドの状態は未定義になります。
522542
523543従って、fork を呼び出す前に初期化されたスレッドは、intialization 節を含め、動作しません。
524544
525545## 並列手続き/ループ
526546
527547マルチスレッドの内の特殊なもので、単一の手続きを並列実行するものです。[[Parallel procedures/ja|並列手続きをご]]覧ください。
528548
529549## 分散コンピューティング
530550
531551マルチスレッドを更に踏み込んだもので、複数のマシンでスレッドを稼動させます。
532552
533553- synapse、lnet、indy のような TCP スイートのいずれかを通信に使用します。これらは柔軟性に富み、クライアントとサーバのアプリケーションの接続の自由度上げたい場合よく用いられます。
534554- [[MPICH]] のようなメッセージパッシングライブラリを使用します。MPICH はコンピュータクラスタ上で高性能計算をするのに使われています。
535555
536556## 外部スレッド
537557
538558Free Pascal のスレッディングシステムを正しく動作させるには、新たに生成された FPC スレッドを初期化する必要があります(より正確には、スレッド毎に例外・I/O・スレッド変数 threadvar システムを初期化し、スレッド変数とヒープを動作可能にしなければなりません)。この過程は BeginThread を(あるいは間接的に TThread クラスを)用いると完全に自動的に行われます。しかし BeginThread を通さす生成されたスレッド(外部スレッド)を利用する場合、追加の作業が(いまのところ)必要となります。外部スレッドには外部の C ライブラリ (.DLL/.so) が生成したものも含みます。
539559
540560外部スレッドを用いる際に考慮すべき点です(将来のコンパイラのヴァージョンでは不要になるかもしれません):
541561
542562- 外部スレッドを一切使わない - FPC スレッドを使う。スレッドの生成を完全に制御できるなら、BeginThread を使って自分でスレッドを生成します。
543563
544564- 呼出規約が一致しない場合(生成しようとするスレッド関数が cdecl 呼出規約を必要としているのに、BeginThread が Pascal 呼出規約を必要としているような場合)、レコード型を一つ作り、オリジナルのスレッド関数をその中に入れて、Pascal スレッド関数側から次のように呼びます:
545565
546566``` pascal
547567type
548568 TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;
549569
550570 PCdeclThreadFuncData = ^TCdeclThreadFuncData;
551571 TCdeclThreadFuncData = record
552572 Func: TCdeclThreadFunc; //cdecl 関数
553573 Data: Pointer; //ユーザデータ
554574 end;
555575
556576// Pascal スレッドは cdecl 関数で呼ばれます
557577function C2P_Translator(FuncData: pointer) : ptrint;
558578var
559579 ThreadData: TCdeclThreadFuncData;
560580begin
561581 ThreadData := PCdeclThreadFuncData(FuncData)^;
562582 Result := ptrint(ThreadData.Func(ThreadData.Data));
563583end;
564584
565585procedure CreatePascalThread;
566586var
567587 ThreadData: PCdeclThreadFuncData;
568588begin
569589 New(ThreadData);
570590 // cdecl スレッド関数でなければなりません
571591 ThreadData^.Func := func;
572592 ThreadData^.Data := user_data;
573593 // Pascal スレッドを作成
574594 BeginThread(@C2P_Translator, ThreadData );
575595end;
576596```
577597
578598- ダミーのスレッドを一つ生成して、FPC のスレッドシステムを初期化します。アプリケーションの中で Pascal スレッドを一つでも生成しないと、スレッドシステムは初期化されず、スレッド変数もヒープも正しく動作しません。
579599
580600``` pascal
581601type
582602 tc = class(tthread)
583603 procedure execute;override;
584604 end;
585605
586606 procedure tc.execute;
587607 begin
588608 end;
589609
590610{ メインプログラム }
591611begin
592612 { スレッドの初期設定 }
593613 with tc.create(false) do
594614 begin
595615 waitfor;
596616 free;
597617 end;
598618 { ... コードが続く }
599619end.
600620```
601621
602622(スレッドシステムの初期化が終わると、ランタイムライブラリはシステム変数 IsMultiThread を true にします。この変数はロックを行うのに FPC のルーチンのそこかしこで用いられます。この変数を手動で設定してはいけません。)
603623
604624- なんらかの理由でこれが上手く行かなかったら、次のコードを外部スレッド関数の中で使ってみてください:
605625
606626``` pascal
607627function ExternalThread(param: Pointer): LongInt; stdcall;
608628var
609629 tm: TThreadManager;
610630begin
611631 GetThreadManager(tm);
612632 tm.AllocateThreadVars;
613633 InitThread(1000000); // ここでスタックサイズの初期値を調節します
614634
615635 { スレッド化されたなにかをここで実行する ... }
616636
617637 Result:=0;
618638end;
619639```
620640
621641### 外部スレッドの確認
622642
623643時として、外部スレッドを取り扱わねばならないのか、はっきり分からないことがあります(例えば、C ライブラリがコールバックを使用しているなど)。これは、以下のようにして解析することができます。
624644
6256451\. アプリケーションの現在のスレッド ID を調べます。
626646
627647``` pascal
628648Win32: GetCurrentThreadID();
629649Darwin: GetThreadID();
630650Linux: TThreadID(pthread_self);
631651```
632652
6336532\. スレッド関数内部で再度現在のスレッド ID を調べ、ステップ 1 の結果と比較します。
634654
635655### タイムスライスの放棄
636656
637657ThreadSwitch 手続きを使ってください。
638658
639659> **Note:** Windows の裏技であるような *Sleep(0)* は使わないでください。これはすべてのプラットフォーム上で動作するものではないのです。
640660
641661(訳注:タイムスライスは、1 回のスレッド処理に割り当てられる時間のことです。ThreadSwitch を実行すると残りの時間を放棄して、待機スレッドがあればそれらを行ってから、なければすぐに制御が戻ってきます。)
642662
643663## 関連項目
644664
645665- [[Streaming components/ja|コンポーネントをストリームする]]
646666- [[Manager Worker Threads System]]
647667- [[Example of multi-threaded application: array of threads/ja|マルチスレッドアプリケーションの例:スレッドの配列]]
648668- [[Parallel procedures/ja|並列手続き]]
649669- [[Main Loop Hooks/ja|メインループフック]]
650670- [[Asynchronous Calls/ja|非同期呼び出し]]
651671
652672---
653673*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).*

Back to history