Sign in Join
Wiki copytemporary

Multithreaded Application Tutorial/ja: what changed

Revision 1 to 2. 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
4949` This binary has no thread support compiled in.`\
5050` Recompile the application with a thread-driver in the program uses clause before other units using thread.`\
5151` 訳:このバイナリはスレッドをサポートしないでコンパイルされています。`\
5252` スレッドドライバをスレッドを使用されているユニットより前のプログラム uses 節に置き、アプリケーションをコンパイルし直してください。`
5353
5454> **Note:** "mcount" が見つからないというようなリンカエラーが発生することがあります。これが発生するのは、マルチスレッドコードを含むユニットを使っていて、cthreads ユニットの追加もしくはスマートリンクの使用が必要な場合です。
5555
5656> **Note:** 手続き SYSTEM_NOTHREADERROR 内でエラー "Project raised exception class 'RunError(232)'" が発生することがあります。これが発生するのは、コード内でスレッドが必要としており、cthreads ユニットの追加が必要な場合です。
5757
5858## TThread クラス
5959
6060サンプルは「examples/multithreading/」にあります。
6161
6262マルチスレッドアプリケーションは TThread クラスを使用すると簡単に作成できます。このクラスを用いると追加のスレッド(メインスレッドと並列に実行する)を作ることができます。そのためには通常、Create コンストラクタと、Execute メソッドの 2 つのメソッドをオーバーライドしなければなりません。
6363
6464コンストラクタは、スレッドを準備する際に使います。必要となる変数ないしプロパティに初期値を設定します。基となる TThread のコンストラクタは Suspended という引数を必要とします。名前からもお分かりのように、Suspended が False の場合、スレッドは作成後直ちに実行されます。逆に Suspended が True の場合、スレッドは停止状態で作成されます。この場合、スレッドを実行するには Start メソッドを呼びます。
6565
6666> **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 に置き換えてください。
6767
6868FPC バージョン 2.0.1 以降の場合、TThread.Create は StackSize という暗黙のパラメータを持っています。必要であれば作成されたスレッドのデフォルトスタックサイズを変更することが可能です。例えばスレッド内で深い再帰呼び出しを行う際には重宝するでしょう。スタックサイズを指定しなかった場合は、OS による規定のスタックサイズが使用されます。
6969
7070スレッドで実行したいコードは、Execute メソッドをオーバーライドして、その中に書きます。
7171
7272TThread クラスは一つの重要なプロパティを持っています。それは、
7373
7474`Terminated : boolean;`
7575
7676です。
7777
7878Terminated のデフォルト値は False です。通常の場合、スレッドはループを持っていますが、Terminated が True になったら、そのループから脱出しなければなりません。したがってループのサイクル毎に、Terminated が True になっていないかチェックする必要があります。もし True になっていたなら、速やかに必要な終了処理を行い、Execute メソッドを終了させなければなりません。ですから、Terminate メソッドを呼び出してもデフォルト状態では何も起きないことを、しっかり覚えておいてください。Execute メソッド自体が自分自身を終了するように明示的に実装しなければなりません。
7979
8080先に説明したように、スレッドはビジュアルコンポーネントを操作すべきではありません。なにかをユーザに表示するためにはメインスレッドで行います。
8181
8282これを行うために TThread には Synchronize というメソッドがあります。このメソッドは引数として、引数を持たないメソッドを 1 つとります。例えば、MyMethod というメソッドをスレッドから実行するには、Synchronize(@MyMethod) として呼び出します。この時スレッドの実行は一時的に停止し、メインスレッドから当該メソッドが実行され、その後スレッド実行が再開されます。
8383
8484Synchronize の正確な動作はプラットフォーム依存しますが、基本的には以下のようになります。
8585
8686- メッセージをメインメッセージキューに投げ、スレッドは休眠状態になります。
8787- メインスレッドでメッセージが処理され、MyMethod が呼ばれます。MyMethod は(デバイスや描画用の)コンテクスト無しで実行されます。つまり、MouseDown イベントや Paint イベント実行中に割り込んで実行されるのではなく、これらのイベントは後で実行されます。
8888- メインスレッドで MyMethod が実行された後、スレッドの休眠状態が解かれ、次のメッセージが処理されます。
8989- スレッドの処理が再開されます。
9090
9191TThread が持つもう一つの重要なプロパティに FreeOnTerminate があります。このプロパティを True にしておくと、スレッドの実行(Execute メソッド)が停止した後にスレッドオブジェクトは自動的に解放されます。これを行わない場合は、アプリケーション側で手動でオブジェクトを解放する必要があります。\
9292(訳注:以下の例では単に FreeOnTerminate = True しているだけなので、スレッドが終了した場合 MyThread 変数が示すアドレスに不正にアクセスできてしまいます。スレッド終了時に必ず呼ばれる TThread.OnTerminate で MyThread := nil するなどして適切な処置を行ってください。OnTerminate イベントも Synchronize を通して呼ばれています。)
9393
9494例:
9595
9696``` pascal
9797 Type
9898 TMyThread = class(TThread)
9999 private
100100 fStatusText : string;
101101 procedure ShowStatus;
102102 protected
103103 procedure Execute; override;
104104 public
105105 Constructor Create(CreateSuspended : boolean);
106106 end;
107107
108108 constructor TMyThread.Create(CreateSuspended : boolean);
109109 begin
110110 FreeOnTerminate := True;
111111 inherited Create(CreateSuspended);
112112 end;
113113
114114 procedure TMyThread.ShowStatus;
115115 // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
116116 begin
117117 Form1.Caption := fStatusText;
118118 end;
119119
120120 procedure TMyThread.Execute;
121121 var
122122 newStatus : string;
123123 begin
124124 fStatusText := 'TMyThread Starting...';
125125 Synchronize(@Showstatus);
126126 fStatusText := 'TMyThread Running...';
127127 while (not Terminated) and ([他のループ終了条件]) do
128128 begin
129129 ...
130130 [ここにスレッドループの本体コードを記述します]
131131 ...
132132 if NewStatus <> fStatusText then
133133 begin
134134 fStatusText := newStatus;
135135 Synchronize(@Showstatus);
136136 end;
137137 end;
138138 end;
139139```
140140
141141アプリケーション側では、
142142
143143``` pascal
144144 var
145145 MyThread : TMyThread;
146146 begin
147147 MyThread := TMyThread.Create(True); // このようにすると自動的に開始しません
148148 ...
149149 [ここにスレッドを実行する前に必要な初期化コードを記述します]
150150 ...
151151 MyThread.Start;
152152 end;
153153```
154154
155155もっと柔軟な制御を望むなら、スレッドに対しイベントを作成します。こうすることで、イベントに対してイベントハンドラで設定できるようになり、Synchronize されたメソッドは特定のフォームやクラスに縛られなくなります。以下が例です。
156156
157157``` pascal
158158 Type
159159 TShowStatusEvent = procedure(Status: String) of Object;
160160
161161 TMyThread = class(TThread)
162162 private
163163 fStatusText : string;
164164 FOnShowStatus: TShowStatusEvent;
165165 procedure ShowStatus;
166166 protected
167167 procedure Execute; override;
168168 public
169169 Constructor Create(CreateSuspended : boolean);
170170 property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
171171 end;
172172
173173 constructor TMyThread.Create(CreateSuspended : boolean);
174174 begin
175175 FreeOnTerminate := True;
176176 inherited Create(CreateSuspended);
177177 end;
178178
179179 procedure TMyThread.ShowStatus;
180180 // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
181181 begin
182182 if Assigned(FOnShowStatus) then
183183 begin
184184 FOnShowStatus(fStatusText);
185185 end;
186186 end;
187187
188188 procedure TMyThread.Execute;
189189 var
190190 newStatus : string;
191191 begin
192192 fStatusText := 'TMyThread Starting...';
193193 Synchronize(@Showstatus);
194194 fStatusText := 'TMyThread Running...';
195195 while (not Terminated) and ([他のループ終了条件]) do
196196 begin
197197 ...
198198 [ここにスレッドループの本体コードを記述します]
199199 ...
200200 if NewStatus <> fStatusText then
201201 begin
202202 fStatusText := newStatus;
203203 Synchronize(@Showstatus);
204204 end;
205205 end;
206206 end;
207207```
208208
209209アプリケーション側では、
210210
211211``` pascal
212212 Type
213213 TForm1 = class(TForm)
214214 Button1: TButton;
215215 Label1: TLabel;
216216 procedure FormCreate(Sender: TObject);
217217 procedure FormDestroy(Sender: TObject);
218218 private
219219 { private declarations }
220220 MyThread: TMyThread;
221221 procedure ShowStatus(Status: string);
222222 public
223223 { public declarations }
224224 end;
225225
226226 procedure TForm1.FormCreate(Sender: TObject);
227227 begin
228228 inherited;
229229 MyThread := TMyThread.Create(true);
230230 MyThread.OnShowStatus := @ShowStatus;
231231 end;
232232
233233 procedure TForm1.FormDestroy(Sender: TObject);
234234 begin
235235 MyThread.Terminate;
236236
237237 // FreeOnTerminate が true であれば、以下の記述は必要ありません。
238238 // MyThread.Free;
239239 inherited;
240240 end;
241241
242242 procedure TForm1.Button1Click(Sender: TObject);
243243 begin
244244 MyThread.Start;
245245 end;
246246
247247 procedure TForm1.ShowStatus(Status: string);
248248 begin
249249 Label1.Caption := Status;
250250 end;
251251```
252252
253253## 特に注意すべきこと
254254
255255### Windows におけるスタックチェック
256256
257257Windows で -Ct スイッチ(スタックチェック)を使うスレッドには頭の痛い問題があります。理由は定かではありませんが、デフォルトのスタックサイズを使う場合、スタックチェックが TTread.Create 内で問題を引き起こすことがあります。現在の運用上の回避策としては -Ct スイッチを使わないことです。一連の現象がメインスレッドで例外を引き起こすことは全くなく、新しく生成されたスレッド内で起こります。そのためスレッドが絶対開始できないように見えます。
258258
259259スレッド生成時に発生するその他の例外や、この問題をチェックする良いコードを示します。
260260
261261``` pascal
262262MyThread := TThread.Create(False);
263263if Assigned(MyThread.FatalException) then
264264 raise MyThread.FatalException;
265265```
266266
267267このコードは、スレッド生成時に発生するどんな例外でも、メインスレッドで生成させます。
268268
269269### パッケージでのマルチスレッド
270270
271271マルチスレッドを使用しているパッケージには、**-dUseCThreads** フラグをカスタム使用オプションに追加してください。パッケージエディタを開いて、オプション → 使用 → カスタム、そこへ *-dUseCThreads* を追加します。これによって、このパッケージを使用している IDE を含めたすべてのプロジェクトおよびパッケージでこのフラグが定義されます。IDE および IDE によって作成されたアプリケーションには、.lpr ファイル内に以下のコードが組み込まれています。(訳注:cmem は自動生成で組み込まれてはいません(Lazarus 1.0.12/1.2RC1 で確認)。)
272272
273273``` pascal
274274uses
275275 {$IFDEF UNIX}{$IFDEF UseCThreads}
276276 cthreads,
277277 cmem, // the c memory manager is on some systems much faster for multi-threading
278278 {$ENDIF}{$ENDIF}
279279```
280280
281281### Heaptrc
282282
283283*cmem* ユニットを使った上で -gh スイッチを使うことはできません。-gh スイッチは、(メモリーリークを調べるために)ヒープマネージャーを拡張する heaptrc ユニットを使用するものです。ですから(リークチェックしたいのであれば)、**cmem** ユニットを使ってから **その後に** **heaptrc** ユニットを置くようにする必要があります。(訳注:heaptrc は cmem を考慮するように作られているけど、cmem はそうではないからでしょう。)
284284
285285``` pascal
286286uses
287287 {$IFDEF UNIX}{$IFDEF UseCThreads}
288288 cthreads,
289289 cmem, // the c memory manager is on some systems much faster for multi-threading
290290 {$ENDIF}{$ENDIF}
291291 heaptrc,
292292```
293293
294294## SMP(対称型マルチプロセッシング)のサポート
295295
296296難しく考える必要はありません。このページで紹介されているマルチスレッドのやり方でアプリケーションを適切な動作をさせればいいのです。SMP は常に有効になっていますから。
297297
298298## Lazarus でのマルチスレッドアプリケーションのデバッグ
299299
300300LazarusでのデバグにはGDBが必要です。Lazarusのデバグ環境はどんどんフル装備かつ安定になっています。しかしながら Linux の一部のディストリビューションには問題をかかえたものもあります。 (* 訳注: MacOSX 10.8以降の場合、Xcode から GDB が失われているため、ユーザが自力で GDB をインストールし、デバグのための特権を与える必要があります *)
301301
302302### デバッグ出力
303303
304304単一スレッドアプリケーションでは、単純にコンソールでもターミナルでも好きなものに出力すれば、出力した順に行が並びます。マルチスレッドアプリケーションでは事態はもっと複雑です。スレッドAがスレッドBより先に一行出力したからといって、その順に書き込まれるとは限りません。あるスレッドが自分の出力を書いているときに、たまたま他のスレッドが行を書き込んでいるかも知れないのです。Linux では(多分)適切なDebugLn() 出力を得られるでしょう。Win32 ではDegubLn() をメインスレッドの外で使っていることによる例外が発生するかもしれません(おそらく DiskFull 例外)。頭痛を軽減するには、下記の DebugLnThreadLog() をお使いください。
305305
306306The LCLProc ユニットには、各スレッドが自身のログファイルを書くための手続き/関数がいくつかあります:
307307
308308``` pascal
309309 procedure DbgOutThreadLog(const Msg: string); overload;
310310 procedure DebuglnThreadLog(const Msg: string); overload;
311311 procedure DebuglnThreadLog(Args: array of const); overload;
312312 procedure DebuglnThreadLog; overload;
313313```
314314
315315例: *writeln('Some text ',123);* の代わりに、これを使います
316316
317317` DebuglnThreadLog(['Some text ',123]);`
318318
319319こうすると、 'Some text 123' という行が **Log\<PID\>.txt** に追加されます。ここで \<PID\> はそのスレッドのプロセス ID です。
320320
321321実行する前毎に、ログファイルを削除するとよいでしょう:
322322
323323` rm -f Log* && ./project1`
324324
325325### Linux
326326
327327Linux でマルチスレッドアプリケーションをデバグする場合、一つの大きな問題があります: Xサーバの Desktop Manager がハングするかもしれません。例えば、デバグ中のアプリケーションがマウスやキーボードを捕獲し、そこで gdb がアプリケーションを一時停止させ、Xサーバがアプリケーションを待ってしまうとこうなります。こうなってしまったら、他のコンピュータからログインするか、CTRL+ALT+F3 でそのセッションから抜け出すかして、gdb を kill してください。あるいは、ウィンドウマネジャを再起動することもできます: 次のように入力してください sudo /etc/init.d/gdm restart。これで Desktop Manager が再起動し、デスクトップに戻れます。
328328
329329この問題は、gdb がデバグ中のプログラムのどこで実行を止めるかに依存するので、ちょっとしたトリックが有効な場合があります: Ubuntu x64 では、プロジェクトオプション → コンパイラオプション → リンク → デバッグ:外部の gdb デバッグシンボルファイルを使用 (-Xg) をチェックします。
330330
331331別のXデスクトップを開く手もあります。IDEとgdbを動かすデスクトップと、アプリケーションを動かすデスクトップを分けるのです。そうすれば固まるのはテスト用のデスクトップだけです。Xの別インスタンスを作るには:
332332
333333` X :1 &`
334334
335335とします。元のデスクトップに戻るには CTRL+ALT+F7を、再び新しいデスクトップに行くには CTRL+ALT+F8 を使います (このキーコンビネーションがうまく行かなかったら、CTRL+ALT+F2 を試してください... [Slackware](http://www.slackware.com)で有効でした)。
336336
337337これで、X上のデスクトップセッションを始められます:
338338
339339` gnome-session --display=:1 &`
340340
341341そしてLazarusでプロジェクトの run parameters dialogで"Use display"をチェックして、:1を入力します。
342342
343343これでアプリケーションは2番目のXサーバーで動作するようになり、最初のXサーバーでデバッグが可能になりました。
344344
345345この方法は、FPC2.0 と Lazarus 0.9.10 で、OS は Windows と Linux でテストしました。
346346
347347------------------------------------------------------------------------
348348
349349新しい X セッションを開始する代わりに、 [Xnest](http://en.wikipedia.org/wiki/Xnest) を用いることもできます。Xnest は X セッションをウィンドウで開くものです。これを用いると、スレッドをデバッグする間も X サーバがロックしません。また、複数のターミナルを行ったり来たりしながらデバグするよりも楽です。
350350
351351Xnestを起動するには、次のようなコマンドをタイプします:
352352
353353` Xnest :1 -ac`
354354
355355一つの X セッションが :1 で開き、アクセス制御がディセーブルされます。
356356
357357## Lazarus ウィジェットセットインターフェース
358358
359359win32、gtk、carbon インターフェースはマルチスレッドをサポートしています。それは TThread、クリティカルセクション、同期動作のことを指しています。しかしながら、これらはスレッドセーフではないのです。つまり、ある瞬間に同時に LCL にアクセスできるスレッドは 1 つに限られているということです。また、メインスレッドが他のスレッドに対して待機するようなことは通常はなく、メインスレッドだけが LCL へのアクセスを許可されているのです。TControl、アプリケーション、LCL ウィジェットの各ハンドルを使って動作させられるのはメインスレッドだけなのです。その一方で、LCL にはスレッドセーフな関数もいくつかあります。例えば、FileUtil ユニット内のほとんどの関数はスレッドセーフです。
360360
361361### スレッド間のやり取りに SendMessage/PostMessage を使用する
362362
3633631 つのアプリケーションにおいて LCL API を呼び出すのはただ 1 つであるべきで、通常はメインスレッドがそれを担っています。いくつかの間接的手段を通すことで、他のスレッドでも LCL の使用が可能になります。SendMessage や PostMessage の使用もそのような手段の一つといえるでしょう。LCLIntf.SendMessage と LCLIntf.PostMessage は、メッセージをアプリケーションのメッセージプールに送り、ウィンドウを直接制御するものです。
364364
365365これらのルーチンのドキュメントもお読みください。
366366
367367- [[doc:lcl/lclintf/sendmessage.html|SendMessage]]
368368- [[doc:lcl/lclintf/postmessage.html|PostMessage]]
369369
370370SendMessage と PostMessage の違いは、呼び出した側への制御の復帰方法です。SendMessage ではメッセージを送った先のウィンドウの処理が完了するまで制御が戻りませんが、PostMessage では即座に制御が戻ります。
371371
372372以下は、別のスレッドよりメインスレッドへ LCL コントロールに表示させるテキストを送る方法の例です。
373373
374374``` pascal
375375const
376376 WM_GOT_ERROR = LM_USER + 2004;
377377 WM_VERBOSE = LM_USER + 2005;
378378
379379procedure VerboseLog(Msg: string);
380380var
381381 PError: PChar;
382382begin
383383 if MessageHandler = 0 then Exit;
384384 PError := StrAlloc(Length(Msg)+1);
385385 StrCopy(PError, PChar(Msg));
386386 PostMessage(formConsole.Handle, WM_VERBOSE, Integer(PError), 0);
387387end;
388388```
389389
390390こちらは、受け取ったメッセージの処理の例です。
391391
392392``` pascal
393393const
394394 WM_GOT_ERROR = LM_USER + 2004;
395395 WM_VERBOSE = LM_USER + 2005;
396396
397397type
398398 { TformConsole }
399399
400400 TformConsole = class(TForm)
401401 DebugList: TListView;
402402 // ...
403403 private
404404 procedure HandleDebug(var Msg: TLMessage); message WM_VERBOSE;
405405 end;
406406
407407var
408408 formConsole: TformConsole;
409409
410410implementation
411411
412412....
413413
414414{ TformConsole }
415415
416416procedure TformConsole.HandleDebug(var Msg: TLMessage);
417417var
418418 Item: TListItem;
419419 MsgStr: PChar;
420420 MsgPasStr: string;
421421begin
422422 MsgStr := PChar(Msg.wparam);
423423 MsgPasStr := StrPas(MsgStr);
424424 Item := DebugList.Items.Add;
425425 Item.Caption := TimeToStr(SysUtils.Now);
426426 Item.SubItems.Add(MsgPasStr);
427427 Item.MakeVisible(False);
428428 //f/TrayControl.SetError(MsgPasStr);
429429end;
430430
431431end.
432432```
433433
434434## クリティカルセクション
435435
436436*クリティカルセクション*はオブジェクトの一つで、ある部分のコードがある時点では 1 つのスレッドだけで実行されること(排他的に実行されること)を保証するものです。クリティカルセクションは使用前に作成/初期化し、使用後は解放する必要があります。
437437
438438以下はクリティカルセクションの一般的な使い方です。
439439
440440セクションを宣言します(すべてのスレッドからアクセスできるようにグローバルにします)。
441441
442442` MyCriticalSection: TRTLCriticalSection;`
443443
444444セクションを作成します。
445445
446446` InitializeCriticalSection(MyCriticalSection);`
447447
448448スレッドを実行します。排他的な処理は以下のようにします。
449449
450450``` pascal
451451EnterCriticalSection(MyCriticalSection);
452452try
453453 // 変数へのアクセス、ファイルの書き出し、ネットワークパケットの送信、など
454454finally
455455 LeaveCriticalSection(MyCriticalSection);
456456end;
457457```
458458
459459すべてのスレッドが停止した後、セクションを解放します。
460460
461461` DeleteCriticalSection(MyCriticalSection);`
462462
463463別の方法として、TCriticalSection オブジェクトも使えます。オプジェクトを作成するとセクションの作成と初期化を、Enter メソッドを実行すると EnterCriticalSection と同様の動作を、Leave メソッドを実行すると LeaveCriticalSection と同様の動作を、オブジェクトを破棄するとセクションの削除を行います。
464464
465465例:5 つのスレッドが 1 つのカウンターに対して増加処理を行います。\
466466lazarus/examples/multithreading/criticalsectionexample1.lpi
467467
468468**注意:**上述の 4 つの手続きは RTL(FPC のランタイム)と LCL に一組ずつ存在します。LCL のは LCLIntf と LCLType ユニットで定義されています(RTL のは System 系ユニット内)。どちらの組も同様に用いることができます。アプリケーションの中で混用することもできますが、RTL の関数/手続きには RTL のクリティカルセクションを、LCL の関数/手続きには LCL のクリティカルセクションを使わなければなりません。
469469
470470### 変数の共用
471471
472472複数のスレッドが 1 つの変数を共用する場合、読み出すだけなら何も問題はありません。単に読めばいいのです。しかし、1 つまたは複数のスレッドがその値を変更しようとするなら、複数のスレッドがその変数に同時にアクセスしないように注意しなければなりません。
473473
474474例:5 つのスレッドが 1 つのカウンターに対して増加処理を行います。\
475475lazarus/examples/multithreading/criticalsectionexample1.lpi
476476
477477## 別のスレッドの処理を待つ方法
478478
479479スレッド A が別のスレッド B の結果を利用するなら、A は B の処理が終了するまで待つ必要があります。
480480
481481**重要:**メインスレッドは決して他のスレッドの処理を待ってはいけません。その代わりに上述の Synchronize を用います。
482482
483483一例:lazarus/examples/multithreading/waitforexample1.lpi
484484
485485``` pascal
486486{ TThreadA }
487487
488488procedure TThreadA.Execute;
489489begin
490490 Form1.ThreadB:=TThreadB.Create(false);
491491 // イベント作成
492492 WaitForB:=RTLEventCreate;
493493 while not Application.Terminated do begin
494494 // B が A を起動させるまで待機し続けます
495495 RtlEventWaitFor(WaitForB);
496496 writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
497497 end;
498498end;
499499
500500{ TThreadB }
501501
502502procedure TThreadB.Execute;
503503var
504504 i: Integer;
505505begin
506506 Counter:=0;
507507 while not Application.Terminated do begin
508508 // B 処理中 ...
509509 Sleep(1500);
510510 inc(Counter);
511511 // A 起動
512512 RtlEventSetEvent(Form1.ThreadA.WaitForB);
513513 end;
514514end;
515515```
516516
517517> **Note:** RtlEventWaitFor の前に RtlEventSetEvent を呼ぶことはできますが、その場合 RtlEventWaitFor は即座に制御を返します。終了フラグをクリアしたいのでしたら RTLeventResetEvent を使用してください。
518518
519519## Fork
520520
521-マルチスレッドアプリケーションにおける fork に関して、次のことに留意してください。fork (あるいはfpFork)を呼び出す<B>前</B>に生成し動作させたあらゆるスレッドは、子プロセスで走っているのでは<B>ありません</B>。fork() の man ページで述べられているように、fork を呼び出す前に走っているスレッドの状態は未定義になります。
521+マルチスレッドアプリケーションにおける fork に関して、次のことに留意してください。fork (あるいはfpFork)を呼び出す前に生成し動作させたあらゆるスレッドは、子プロセスで走っているのではありません。fork() の man ページで述べられているように、fork を呼び出す前に走っているスレッドの状態は未定義になります。
522522
523523従って、fork を呼び出す前に初期化されたスレッドは、intialization 節を含め、動作しません。
524524
525525## 並列手続き/ループ
526526
527527マルチスレッドの内の特殊なもので、単一の手続きを並列実行するものです。[[Parallel procedures/ja|並列手続きをご]]覧ください。
528528
529529## 分散コンピューティング
530530
531531マルチスレッドを更に踏み込んだもので、複数のマシンでスレッドを稼動させます。
532532
533533- synapse、lnet、indy のような TCP スイートのいずれかを通信に使用します。これらは柔軟性に富み、クライアントとサーバのアプリケーションの接続の自由度上げたい場合よく用いられます。
534534- [[MPICH]] のようなメッセージパッシングライブラリを使用します。MPICH はコンピュータクラスタ上で高性能計算をするのに使われています。
535535
536536## 外部スレッド
537537
538538Free Pascal のスレッディングシステムを正しく動作させるには、新たに生成された FPC スレッドを初期化する必要があります(より正確には、スレッド毎に例外・I/O・スレッド変数 threadvar システムを初期化し、スレッド変数とヒープを動作可能にしなければなりません)。この過程は BeginThread を(あるいは間接的に TThread クラスを)用いると完全に自動的に行われます。しかし BeginThread を通さす生成されたスレッド(外部スレッド)を利用する場合、追加の作業が(いまのところ)必要となります。外部スレッドには外部の C ライブラリ (.DLL/.so) が生成したものも含みます。
539539
540540外部スレッドを用いる際に考慮すべき点です(将来のコンパイラのヴァージョンでは不要になるかもしれません):
541541
542542- 外部スレッドを一切使わない - FPC スレッドを使う。スレッドの生成を完全に制御できるなら、BeginThread を使って自分でスレッドを生成します。
543543
544544- 呼出規約が一致しない場合(生成しようとするスレッド関数が cdecl 呼出規約を必要としているのに、BeginThread が Pascal 呼出規約を必要としているような場合)、レコード型を一つ作り、オリジナルのスレッド関数をその中に入れて、Pascal スレッド関数側から次のように呼びます:
545545
546546``` pascal
547547type
548548 TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;
549549
550550 PCdeclThreadFuncData = ^TCdeclThreadFuncData;
551551 TCdeclThreadFuncData = record
552552 Func: TCdeclThreadFunc; //cdecl 関数
553553 Data: Pointer; //ユーザデータ
554554 end;
555555
556556// Pascal スレッドは cdecl 関数で呼ばれます
557557function C2P_Translator(FuncData: pointer) : ptrint;
558558var
559559 ThreadData: TCdeclThreadFuncData;
560560begin
561561 ThreadData := PCdeclThreadFuncData(FuncData)^;
562562 Result := ptrint(ThreadData.Func(ThreadData.Data));
563563end;
564564
565565procedure CreatePascalThread;
566566var
567567 ThreadData: PCdeclThreadFuncData;
568568begin
569569 New(ThreadData);
570570 // cdecl スレッド関数でなければなりません
571571 ThreadData^.Func := func;
572572 ThreadData^.Data := user_data;
573573 // Pascal スレッドを作成
574574 BeginThread(@C2P_Translator, ThreadData );
575575end;
576576```
577577
578578- ダミーのスレッドを一つ生成して、FPC のスレッドシステムを初期化します。アプリケーションの中で Pascal スレッドを一つでも生成しないと、スレッドシステムは初期化されず、スレッド変数もヒープも正しく動作しません。
579579
580580``` pascal
581581type
582582 tc = class(tthread)
583583 procedure execute;override;
584584 end;
585585
586586 procedure tc.execute;
587587 begin
588588 end;
589589
590590{ メインプログラム }
591591begin
592592 { スレッドの初期設定 }
593593 with tc.create(false) do
594594 begin
595595 waitfor;
596596 free;
597597 end;
598598 { ... コードが続く }
599599end.
600600```
601601
602602(スレッドシステムの初期化が終わると、ランタイムライブラリはシステム変数 IsMultiThread を true にします。この変数はロックを行うのに FPC のルーチンのそこかしこで用いられます。この変数を手動で設定してはいけません。)
603603
604604- なんらかの理由でこれが上手く行かなかったら、次のコードを外部スレッド関数の中で使ってみてください:
605605
606606``` pascal
607607function ExternalThread(param: Pointer): LongInt; stdcall;
608608var
609609 tm: TThreadManager;
610610begin
611611 GetThreadManager(tm);
612612 tm.AllocateThreadVars;
613613 InitThread(1000000); // ここでスタックサイズの初期値を調節します
614614
615615 { スレッド化されたなにかをここで実行する ... }
616616
617617 Result:=0;
618618end;
619619```
620620
621621### 外部スレッドの確認
622622
623623時として、外部スレッドを取り扱わねばならないのか、はっきり分からないことがあります(例えば、C ライブラリがコールバックを使用しているなど)。これは、以下のようにして解析することができます。
624624
6256251\. アプリケーションの現在のスレッド ID を調べます。
626626
627627``` pascal
628628Win32: GetCurrentThreadID();
629629Darwin: GetThreadID();
630630Linux: TThreadID(pthread_self);
631631```
632632
6336332\. スレッド関数内部で再度現在のスレッド ID を調べ、ステップ 1 の結果と比較します。
634634
635635### タイムスライスの放棄
636636
637637ThreadSwitch 手続きを使ってください。
638638
639639> **Note:** Windows の裏技であるような *Sleep(0)* は使わないでください。これはすべてのプラットフォーム上で動作するものではないのです。
640640
641641(訳注:タイムスライスは、1 回のスレッド処理に割り当てられる時間のことです。ThreadSwitch を実行すると残りの時間を放棄して、待機スレッドがあればそれらを行ってから、なければすぐに制御が戻ってきます。)
642642
643643## 関連項目
644644
645645- [[Streaming components/ja|コンポーネントをストリームする]]
646646- [[Manager Worker Threads System]]
647647- [[Example of multi-threaded application: array of threads/ja|マルチスレッドアプリケーションの例:スレッドの配列]]
648648- [[Parallel procedures/ja|並列手続き]]
649649- [[Main Loop Hooks/ja|メインループフック]]
650650- [[Asynchronous Calls/ja|非同期呼び出し]]
651651
652652---
653653*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