Difference between revisions of "Multithreaded Application Tutorial/ja"

From Lazarus wiki
Jump to navigationJump to search
m (Text replace - "Delphi>" to "syntaxhighlight>")
Line 17: Line 17:
  
 
== マルチスレッディングが必要ですか? ==
 
== マルチスレッディングが必要ですか? ==
 +
 
もし読者がマルチスレッディングにこれまで触れたことがなく、単に重い処理を実行するアプリケーションの応答性を改善したいだけなら、マルチスレッディングはお探しのものとは違うかもしれません。マルチスレッドアプリケーションはしばしば複雑であり、デバグは常に困難です。また、多くの場合マルチスレッディングは必要のないものです。単一スレッドアプリケーションで十分です。時間のかかるタスクを細かな部分に分割することができるなら、マルチスレッディングの代わりに<tt> Application.ProcessMessages </tt>を使うべきです。このメソッドは LCL に待機中のメッセージを全て処理させるものです。分割したタスクの一部分を実行したら Application.ProcessMessages を呼び、ユーザがアプリケーションを終了させたりどこかをクリックしたりしたかを確認し、あるいは進行状況表示を再描画したりします。その後タスクの次の部分を実行し、また Application.ProcessMessages を呼びます。
 
もし読者がマルチスレッディングにこれまで触れたことがなく、単に重い処理を実行するアプリケーションの応答性を改善したいだけなら、マルチスレッディングはお探しのものとは違うかもしれません。マルチスレッドアプリケーションはしばしば複雑であり、デバグは常に困難です。また、多くの場合マルチスレッディングは必要のないものです。単一スレッドアプリケーションで十分です。時間のかかるタスクを細かな部分に分割することができるなら、マルチスレッディングの代わりに<tt> Application.ProcessMessages </tt>を使うべきです。このメソッドは LCL に待機中のメッセージを全て処理させるものです。分割したタスクの一部分を実行したら Application.ProcessMessages を呼び、ユーザがアプリケーションを終了させたりどこかをクリックしたりしたかを確認し、あるいは進行状況表示を再描画したりします。その後タスクの次の部分を実行し、また Application.ProcessMessages を呼びます。
  
Line 26: Line 27:
 
* APIを呼び出さなければならないためにそれ以上細かく分割できないアルゴリズムやライブラリの呼び出し(訳者注:そのような分割できない長い処理を普通に行うと、ハングアップして固まったかのように見えてしまいますから、処理時間が長くかかる処理は別スレッドで処理して、描画やウィンドウの移動などの操作にはメインスレッドで反応するような使い方が考えられます)。
 
* APIを呼び出さなければならないためにそれ以上細かく分割できないアルゴリズムやライブラリの呼び出し(訳者注:そのような分割できない長い処理を普通に行うと、ハングアップして固まったかのように見えてしまいますから、処理時間が長くかかる処理は別スレッドで処理して、描画やウィンドウの移動などの操作にはメインスレッドで反応するような使い方が考えられます)。
  
If You want to use multi-threading to increase speed by using multiple processors simultaneously, check if your current program now use all 100% resources of 1 core CPU (for example, you program can actively use input-output operations, e.g. writing to file; this takes a lot of time, but doesn't load CPU; in this case you program will not be faster with multiple threads). Also check if optimisation level is set to maximum (3). When I switched optimisation level from 1 to 3, my program became about 5 times faster.
+
If you want to use multi-threading to increase speed by using multiple processors simultaneously, check if your current program now use all 100% resources of 1 core CPU (for example, you program can actively use input-output operations, e.g. writing to file; this takes a lot of time, but doesn't load CPU; in this case you program will not be faster with multiple threads). Also check if optimisation level is set to maximum (3). When I switched optimisation level from 1 to 3, my program became about 5 times faster.
 +
 
 +
==マルチスレッドアプリケーションで必要となるユニット==
 +
You don´t need any special unit for this to work with Windows.
 +
However with Linux, Mac OS X and FreeBSD, you need the cthreads unit and it ''must'' be the first used unit of the project (the program source, usually the .lpr file)!
 +
 
 +
So, your Lazarus application code should look like:
 +
 
 +
<syntaxhighlight>
 +
program MyMultiThreadedProgram;
 +
{$mode objfpc}{$H+}
 +
uses
 +
{$ifdef unix}
 +
  cthreads,
 +
  cmem, // the c memory manager is on some systems much faster for multi-threading
 +
{$endif}
 +
  Interfaces, // これには LCL ウィジェットセットが含まれています
 +
  Forms
 +
  { お使いになられているユニットをここに追加します },
 +
</syntaxhighlight>
 +
 
 +
If you forget this and you use TThread you will get this error on startup:
 +
  This binary has no thread support compiled in.
 +
  Recompile the application with a thread-driver in the program uses clause before other units using thread.
 +
 
 +
{{Note|If you get a Linker error about "mcount" not found. Then you use some unit that contains some multithreaded code and you need to add the cthreads unit or use smart linking.}}
 +
{{Note|If you get the error: "Project raised exception class 'RunError(232)'" in procedure SYSTEM_NOTHREADERROR then your code requires threading and you need to add the cthreads unit.}}
  
 
== TThreadクラス ==
 
== TThreadクラス ==
 +
  
 
このサンプルは「examples/multithreading/」にあります。
 
このサンプルは「examples/multithreading/」にあります。
Line 36: Line 64:
 
そのためには通常、Create コンストラクタと、Execute メソッドの二つのメソッドをオーバーライドしなければなりません。
 
そのためには通常、Create コンストラクタと、Execute メソッドの二つのメソッドをオーバーライドしなければなりません。
  
コンストラクタは、あなたが走らせるスレッドを準備する際に使います。あなたは変数もしくはプロパティに適切な初期値を納める必要があります。TThread のオリジナルのコンストラクタは Suspended というパラメータを必要とします。名前からもおわかりのように、Suspended が False の場合、スレッドは作成後直ちに実行されます。逆に Suspended が True の場合、スレッドは停止状態で作成されます。この場合、スレッドを走らせるには Resume メソッドを呼びます。
+
コンストラクタは、あなたが走らせるスレッドを準備する際に使います。あなたは変数もしくはプロパティに適切な初期値を納める必要があります。TThread のオリジナルのコンストラクタは Suspended というパラメータを必要とします。名前からもおわかりのように、Suspended が False の場合、スレッドは作成後直ちに実行されます。逆に Suspended が True の場合、スレッドは停止状態で作成されます。この場合、スレッドを走らせるには Start メソッドを呼びます。
 +
 
 +
{{Note|メソッド Resume は [http://wiki.freepascal.org/User_Changes_2.4.4#TThread.Suspend_and_TThread.Resume_have_been_deprecated FPC 2.4.4 以降非推奨になりました]。Resume は Start に置き換えてください。}}
  
 
FPCのバージョンが2.0.1またはそれ以降の場合、TThread.Create は StackSize という暗黙のパラメータを持っています。あなたは必要ならば自分の作るスレッドのデフォルトスタックサイズを変更することが可能です。
 
FPCのバージョンが2.0.1またはそれ以降の場合、TThread.Create は StackSize という暗黙のパラメータを持っています。あなたは必要ならば自分の作るスレッドのデフォルトスタックサイズを変更することが可能です。
 
例えばスレッド内で深い再帰呼び出しを行う際には重宝するでしょう。あなたがスタックサイズを指定しなければ、OSによる規定のスタックサイズが使用されます。
 
例えばスレッド内で深い再帰呼び出しを行う際には重宝するでしょう。あなたがスタックサイズを指定しなければ、OSによる規定のスタックサイズが使用されます。
  
スレッドで実行したいコードは、Execute メソッドをオーヴァーライドして、その中に書きます。
+
スレッドで実行したいコードは、Execute メソッドをオーバーライドして、その中に書きます。
  
 
TThread クラスは一つの重要なプロパティを持っています。それは:
 
TThread クラスは一つの重要なプロパティを持っています。それは:
Line 49: Line 79:
 
Terminated のデフォルト値は False です。通常の場合、スレッドはループを持っていますが、Terminated が True になったら、そのループから脱出しなければなりません。したがってループのサイクル毎に、Terminatedが True になっていないかチェックする必要があります。もし True になっていたなら、速やかに必要なクリーンアップを行い、Execute メソッドを終了させなければなりません。
 
Terminated のデフォルト値は False です。通常の場合、スレッドはループを持っていますが、Terminated が True になったら、そのループから脱出しなければなりません。したがってループのサイクル毎に、Terminatedが True になっていないかチェックする必要があります。もし True になっていたなら、速やかに必要なクリーンアップを行い、Execute メソッドを終了させなければなりません。
  
ですから、Terminated メソッドを呼び出してもデフォルト状態では何も起きないことを、しっかり覚えておいてください。Execute メソッド自体が自分自身を終了するように明示的に実装しなければなりません。
+
As we explained earlier, the thread should not interact with the visible components. Updates to visible components must be made within the context of the main thread.
  
先に説明したように、スレッドは可視コンポーネントを操作すべきではありません。なにかをユーザーに表示するためにはメインスレッドで行います。これを行うために TThread には<tt> Synchronize </tt>というメソッドがあります。このメソッドは引数として、引数をもたない一つのメソッドをとります。たとえばMyMethodというメソッドをスレッドから実行するには、Synchronixe(@MyMethod)として呼び出します。この時スレッドの実行は一時的に停止し、メインスレッドから当該メソッドが実行され、その後スレッド実行が再開されます。このような仕組みのおかげで、MyMethodはコンテクスト(文脈)を持たずに実行されます。つまり、mousedownイヴェントやpaintイヴェント実行中に割り込んで実行されるのではなく、それらのイヴェントの処理が終わってから実行されます。MyMethodが実行された後、休眠状態になっていたスレッドは再開し、次のメッセージが処理されます。
+
To do this, a TThread method called Synchronize exists. Synchronize requires a method within the thread (that takes no parameters) as an argument.  When you call that method through Synchronize(@MyMethod), the thread execution will be paused, the code of MyMethod will be called from the main thread, and then the thread execution will be resumed.
  
TThread が持つもう一つの重要なプロパティに、FreeOnTerminate があります。このプロパティを True にしておくと、スレッドオブジェクトはスレッドの実行(Executeメソッド)が停止した後に自動的に解放されます。さもなくば、アプリケーションは手動でオブジェクトを解放する必要があります。
+
The exact working of Synchronize depends on the platform, but basically it does this:
 +
* it posts a message onto the main message queue and goes to sleep
 +
* eventually the main thread processes the message and calls MyMethod. This way MyMethod is called without context, that means not during a mouse down event or during paint event, but after.
 +
* after the main thread executed MyMethod, it wakes the sleeping Thread and processes the next message
 +
* the Thread then continues.
 +
 
 +
There is another important property of TThread: FreeOnTerminate. If this property is true, the thread object is automatically freed when the thread execution (.Execute method) stops. Otherwise the application will need to free it manually.
  
 
例:
 
例:
  
<syntaxhighlight>
+
<syntaxhighlight> Type
  Type
 
 
     TMyThread = class(TThread)
 
     TMyThread = class(TThread)
 
     private
 
     private
Line 76: Line 111:
  
 
   procedure TMyThread.ShowStatus;
 
   procedure TMyThread.ShowStatus;
   // このメソッドはメインスレッドで実行されるので GUI 要素にアクセスすることができます
+
   // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
 
   begin
 
   begin
 
     Form1.Caption := fStatusText;
 
     Form1.Caption := fStatusText;
Line 91: Line 126:
 
       begin
 
       begin
 
         ...
 
         ...
         [here goes the code of the main thread loop]
+
         [ここにスレッドループの本体コードを記述します]
 
         ...
 
         ...
 
         if NewStatus <> fStatusText then
 
         if NewStatus <> fStatusText then
Line 108: Line 143:
 
     MyThread : TMyThread;
 
     MyThread : TMyThread;
 
   begin
 
   begin
     MyThread := TMyThread.Create(True); // This way it doesn't start automatically
+
     MyThread := TMyThread.Create(True); // このようにすると自動的に開始しません
 
     ...
 
     ...
     [Here the code initialises anything required before the threads starts executing]
+
     [ここにスレッドを実行する前に必要な初期化コードを記述します]
 
     ...
 
     ...
     MyThread.Resume;
+
     MyThread.Start;
   end;
+
   end;</syntaxhighlight>
</syntaxhighlight>
 
  
もっと柔軟な制御を望むなら、スレッドに対しイヴェントを発行することができ - そうすると、sychronizeされたメソッドは特定のフォームやクラスに縛られなくなります - そのイヴェントに対するリスナーを設定することができます。次の例で、TMyThread.ShowStatusはメインスレッドで実行されるので、全てのGUI要素に触ることができます:
+
もっと柔軟な制御を望むなら、スレッドに対しイベントを発行することができ - そうすると、sychronize されたメソッドは特定のフォームやクラスに縛られなくなります - そのイベントに対するリスナーを設定することができます。次の例で、TMyThread.ShowStatus はメインスレッドで実行されるので、すべての GUI 要素に触ることができます:
  
<syntaxhighlight>
+
<syntaxhighlight> Type
  Type
 
 
     TShowStatusEvent = procedure(Status: String) of Object;
 
     TShowStatusEvent = procedure(Status: String) of Object;
  
Line 141: Line 174:
  
 
   procedure TMyThread.ShowStatus;
 
   procedure TMyThread.ShowStatus;
   // this method is executed by the mainthread and can therefore access all GUI elements.
+
   // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
 
   begin
 
   begin
 
     if Assigned(FOnShowStatus) then
 
     if Assigned(FOnShowStatus) then
Line 156: Line 189:
 
     Synchronize(@Showstatus);
 
     Synchronize(@Showstatus);
 
     fStatusText := 'TMyThread Running...';
 
     fStatusText := 'TMyThread Running...';
     while (not Terminated) and ([any condition required]) do
+
     while (not Terminated) and ([他のループ終了条件]) do
 
       begin
 
       begin
 
         ...
 
         ...
         [here goes the code of the main thread loop]
+
         [ここにスレッドループの本体コードを記述します]
 
         ...
 
         ...
 
         if NewStatus <> fStatusText then
 
         if NewStatus <> fStatusText then
Line 172: Line 205:
 
アプリケーション側では,
 
アプリケーション側では,
  
<syntaxhighlight>
+
<syntaxhighlight> Type
  Type
 
 
     TForm1 = class(TForm)
 
     TForm1 = class(TForm)
 
       Button1: TButton;
 
       Button1: TButton;
Line 197: Line 229:
 
   begin
 
   begin
 
     MyThread.Terminate;
 
     MyThread.Terminate;
     MyThread.Free;
+
 
 +
     // FreeOnTerminate is true so we should not write:
 +
    // MyThread.Free;
 
     inherited;
 
     inherited;
 
   end;
 
   end;
Line 203: Line 237:
 
   procedure TForm1.Button1Click(Sender: TObject);
 
   procedure TForm1.Button1Click(Sender: TObject);
 
   begin
 
   begin
   MyThread.Resume;
+
   MyThread.Start;
 
   end;
 
   end;
  
Line 209: Line 243:
 
   begin
 
   begin
 
     Label1.Caption := Status;
 
     Label1.Caption := Status;
   end;
+
   end;</syntaxhighlight>
</syntaxhighlight>
 
  
 
(日本語訳注: FreeOnTerminate = True で使用した場合に厄介なのはメイン側からスレッドを制御するのが非常に困難になることです。例えば上のコード例で、FormDestroy や Button1Click が呼ばれる前に MyThread.Execute が終了してしまって MyThread が自動解放された場合、正常に動作するのか疑問が残ります。FreeOnTerminate = False のままで使用して、スレッドをメインスレッドで確実に制御するやり方のほうがより実用的でバグもでにくいのでお勧めです。スレッドが止まってもメモリが解放されない点がデメリットといえばデメリットですが、工夫によりさほど気にならないメモリ消費量で済むのではないでしょうか。
 
(日本語訳注: FreeOnTerminate = True で使用した場合に厄介なのはメイン側からスレッドを制御するのが非常に困難になることです。例えば上のコード例で、FormDestroy や Button1Click が呼ばれる前に MyThread.Execute が終了してしまって MyThread が自動解放された場合、正常に動作するのか疑問が残ります。FreeOnTerminate = False のままで使用して、スレッドをメインスレッドで確実に制御するやり方のほうがより実用的でバグもでにくいのでお勧めです。スレッドが止まってもメモリが解放されない点がデメリットといえばデメリットですが、工夫によりさほど気にならないメモリ消費量で済むのではないでしょうか。
Line 305: Line 338:
  
 
== 特別に注意すべきこと ==
 
== 特別に注意すべきこと ==
 +
 
=== Windows におけるスタックチェック ===
 
=== Windows におけるスタックチェック ===
 +
 
Windowsで-Ctスイッチ(スタックチェック)をつかうスレッドには頭の痛い問題があります。理由は定かではありませんが、デフォルトのスタックサイズを使う場合、スタックチェックが TTread.Create をトリガーしてしまうことがあります。現在の運用上の回避策としては -Ctスイッチを使わないことです。これがメインスレッドで例外をひきおこすことは全くなく、新しく生成されたスレッドで起こります。そのためスレッドが絶対開始できないように見えます。
 
Windowsで-Ctスイッチ(スタックチェック)をつかうスレッドには頭の痛い問題があります。理由は定かではありませんが、デフォルトのスタックサイズを使う場合、スタックチェックが TTread.Create をトリガーしてしまうことがあります。現在の運用上の回避策としては -Ctスイッチを使わないことです。これがメインスレッドで例外をひきおこすことは全くなく、新しく生成されたスレッドで起こります。そのためスレッドが絶対開始できないように見えます。
  
 
スレッド生成時に発生するその他の例外や、この問題をチェックする良いコードを示します。
 
スレッド生成時に発生するその他の例外や、この問題をチェックする良いコードを示します。
  
    MyThread:=TThread.Create(False);
+
<syntaxhighlight>MyThread := TThread.Create(False);
    if Assigned(MyThread.FatalException) then
+
if Assigned(MyThread.FatalException) then
      raise MyThread.FatalException;
+
  raise MyThread.FatalException;</syntaxhighlight>
  
 
このコードはスレッド生成時に発生するどんな例外でも、メインのスレッドでraiseするようにします。
 
このコードはスレッド生成時に発生するどんな例外でも、メインのスレッドでraiseするようにします。
 
== マルチスレッドアプリケーションで必要なユニット ==
 
Windowsでは特に気をつけることはないのですが、LinuxやMaxOSX,FreeBSDでは、かならずプロジェクトのユニット(つまり、プログラムユニット、.lpr)で、cthreadsユニットをusesする必要があります。
 
 
Lazarusアプリケーションのコードでは次のようになるでしょう。
 
 
<syntaxhighlight>
 
program MyMultiThreadedProgram;
 
{$mode objfpc}{$H+}
 
uses
 
{$ifdef unix}
 
  cthreads,
 
  cmem, // the c memory manager is on some systems much faster for multi-threading
 
{$endif}
 
  Interfaces, // this includes the LCL widgetset
 
  Forms
 
  { you can add units here },
 
</syntaxhighlight>
 
 
これを怠ると、起動時に次のエラーが出ます:
 
  This binary has no thread support compiled in.
 
  Recompile the application with a thread-driver in the program uses clause before other units using thread.
 
  
 
=== Multithreading in packages ===
 
=== Multithreading in packages ===
Line 344: Line 357:
 
   {$IFDEF UNIX}{$IFDEF UseCThreads}
 
   {$IFDEF UNIX}{$IFDEF UseCThreads}
 
   cthreads,
 
   cthreads,
   cmem, // the c memory manager is on some systems much faster for multi-threading
+
   cmem, // C のメモリマネージャは、特定のシステムのマルチスレッドにおいてとても高速に動作します
 
   {$ENDIF}{$ENDIF}
 
   {$ENDIF}{$ENDIF}
</DELPHI>
+
</syntaxhighlight>
  
 
===Heaptrc===
 
===Heaptrc===
  
You can not use the -gh switch with the ''cmem'' unit. The -gh switch uses the heaptrc unit, which extends the heap manager. Therefore the '''heaptrc''' unit must be used '''after''' the '''cmem''' unit.
+
''cmem'' ユニットを使った上で -gh スイッチを使うことはできません。-gh スイッチは、(メモリーリークを調べるために)ヒープマネージャーを拡張する heaptrc ユニットを使用するものです。ですから(リークチェックしたいのであれば)、'''cmem''' ユニットを使ってから '''その後に''' '''heaptrc''' ユニットを置くようにする必要があります。(訳注:heaptrc は cmem を考慮するように作られているけど、cmem はそうではないからでしょう。)
  
 
<syntaxhighlight>uses
 
<syntaxhighlight>uses
 
   {$IFDEF UNIX}{$IFDEF UseCThreads}
 
   {$IFDEF UNIX}{$IFDEF UseCThreads}
 
   cthreads,
 
   cthreads,
   cmem, // the c memory manager is on some systems much faster for multi-threading
+
   cmem, // C のメモリマネージャは、特定のシステムのマルチスレッドにおいてとても高速に動作します
 
   {$ENDIF}{$ENDIF}
 
   {$ENDIF}{$ENDIF}
   heaptrc,</DELPHI>
+
   heaptrc,</syntaxhighlight>
  
 
== SMP(対称型マルチプロセッサCPU)のサポート ==
 
== SMP(対称型マルチプロセッサCPU)のサポート ==
 
良いニュースです。この用法でマルチスレッドでアプリケーションとして正しく動作すれば、それはSMPで並列動作が有効になります。
 
良いニュースです。この用法でマルチスレッドでアプリケーションとして正しく動作すれば、それはSMPで並列動作が有効になります。
  
== Lazarusでのマルチスレッドのデバッグ ==
+
==Lazarus でのマルチスレッドアプリケーションのデバッグ==
Lazarusでのマルチスレッドのデバッグは、まだ完全に機能しません。
+
The debugging on Lazarus requires GDB and is rapidly becoming more and more fully featured and stable.  However, there still exists a few Linux distributions with some problems.
  
=== デバグ出力 ===
+
===デバッグ出力===
シングルスレッドアプリケーションでは、単にコンソール/ターミナル/その他に一行一行出力すれば、その順序に書き込まれます。マルチスレッドアプリケーションではそうは問屋が卸しません。二つのスレッドA,Bがあり、まずAが、次いでBが行を出力したとします。しかし、必ずしもその順序で行が並ぶわけではありません。さらには、行の途中で別のスレッドの出力が割り込んでくることだってあり得ます。
+
In a single threaded application, you can simply write to console/terminal/whatever and the order of the lines is the same as they were written.
 +
In multi-threaded application things are more complicated. If two threads are writing, say a line is written by thread A before a line by thread B, then the lines are not necessarily written in that order. It can even happen, that a thread writes its output, while the other thread is writing a line.While under linux (maybe) you'll get proper DebugLn() output, under win32 you can get exceptions (probably DiskFull) because of DebugLn() usage outside of main thread.So, to avoid headaches use DebugLnThreadLog() mentioned below.
  
LCLProcユニットに、スレッド毎にログを保存するための手続きがあります:
+
The LCLProc unit contains several functions, to let each thread write to its own log file:
  procedure DbgOutThreadLog(const Msg: string); overload;
+
<syntaxhighlight>  procedure DbgOutThreadLog(const Msg: string); overload;
 
   procedure DebuglnThreadLog(const Msg: string); overload;
 
   procedure DebuglnThreadLog(const Msg: string); overload;
 
   procedure DebuglnThreadLog(Args: array of const); overload;
 
   procedure DebuglnThreadLog(Args: array of const); overload;
   procedure DebuglnThreadLog; overload;
+
   procedure DebuglnThreadLog; overload;</syntaxhighlight>
  
 
例:
 
例:
Line 384: Line 398:
  
 
=== Linux ===
 
=== Linux ===
もしマルチスレッドアプリケーションをLinuxでデバッグしようとすると、Xサーバーがハングするといった大きな問題に直面するでしょう。
+
If you try to debug a multi-threaded application on Linux, you will have one big problem: the Desktop Manager on X server can hang. This happens for instance when the application has captured the mouse/keyboard and was paused by gdb and the X server waits for your application. When that happens you can simply log in from another computer and kill the gdb or exit out of that session by pressing CTRL+ALT+F3 and kill gdb. Alternatively you can restart the window manager: enter sudo /etc/init.d/gdm restart. This will restart the desktop manager and get you back into your desktop.
  
これはどうやって解決すべきか分かっていませんが、回避策としては、Xのインスタンスを次のように新しく作ります。
+
Since it depends where gdb stops your program in some cases some tricks may help: for Ubuntu x64 set the Project options for debugging required extra information file...
 +
 
 +
  プロジェクトオプション → コンパイラオプション → リンク → デバッグ:外部の gdb デバッグシンボルファイルを使用 (-Xg) をチェックします。
 +
 
 +
The other option is to open anotner X desktop, run the IDE/gdb on one and the application on the other, so that only the test desktop freezes. Create a new instance of X with:
  
 
   X :1 &
 
   X :1 &
  
これでOpenして、他のデスクトップへスイッチしたとき、(the one you are working with pressing CTRL+ALT+F7), 元のグラフィカルデスクトップへCTRL+ALT+F8で戻ることができます。
+
It will open, and when you switch to another desktop (the one you are working with pressing CTRL+ALT+F7), you will be able to go back to the new graphical desktop with CTRL+ALT+F8 (if this combination does not work, try with CTRL+ALT+F2... this one worked on [http://www.slackware.com Slackware]).
(もしこの組み合わせがうまくいかなかったらSlackwareでのCTRL+ALT+F2で)
 
  
もし、これができたら、Xが開始するデスクトップセッションを次のようにして作ります。
+
Then you could, if you want, create a desktop session on the X started with:
  
 
   gnome-session --display=:1 &
 
   gnome-session --display=:1 &
Line 402: Line 419:
  
 
この方法は、FPC2.0とLazarus 0.9.10で、OSはWindowsとLinuxでテストしました。
 
この方法は、FPC2.0とLazarus 0.9.10で、OSはWindowsとLinuxでテストしました。
 +
  
 
----
 
----
Line 413: Line 431:
 
一つの X セッションが :1 で開き、アクセス制御がディセーブルされます。
 
一つの X セッションが :1 で開き、アクセス制御がディセーブルされます。
  
== ウィジェットセット ==
+
== Lazarus Widgetset Interfaces ==
 +
The win32, the gtk and the carbon interfaces support multi-threading. This means, TThread, critical sections and Synchronize work. But they are not thread safe. This means only one thread at a time can access the LCL. And since the main thread should never wait for another thread, it means only the main thread is allowed to access the LCL, which means anything that has to do with TControl, Application and LCL widget handles.
 +
There are some thread safe functions in the LCL. For example most of the functions in the FileUtil unit are thread safe.
  
Win32、gtk、Carbon インタフェースはマルチスレッディングを完全にサポートしています。即ち TThread、クリティカル・セクション、Synchronize が動作します。
+
===スレッド間のやり取りに SendMessage/PostMessage を使用する===
 
 
===Using SendMessage/PostMessage to communicate between threads===
 
  
 
Only one thread in an application should call LCL APIs, usually the main thread. Other threads can make use of the LCL through a number of indirect methods, one good option being the usage of SendMessage or PostMessage. LCLIntf.SendMessage and LCLIntf.PostMessage will post a message directed to a window in the message pool of the application.
 
Only one thread in an application should call LCL APIs, usually the main thread. Other threads can make use of the LCL through a number of indirect methods, one good option being the usage of SendMessage or PostMessage. LCLIntf.SendMessage and LCLIntf.PostMessage will post a message directed to a window in the message pool of the application.
  
See also the documentation for these routines:
+
これらのルーチンのドキュメントもお読みください。
*http://lazarus-ccr.sourceforge.net/docs/lcl/lclintf/sendmessage.html
+
*[[doc:lcl/lclintf/sendmessage.html|SendMessage]]
*http://lazarus-ccr.sourceforge.net/docs/lcl/lclintf/postmessage.html
+
*[[doc:lcl/lclintf/postmessage.html|PostMessage]]
  
The difference between SendMessage and PostMessage is the way that they return control to the calling thread. With SendMessage control is not returned until the window that the message was sent to has completed processing the sent message, however with PostMessage control is returned immediately.
+
SendMessage PostMessage の違いは、呼び出した側への制御の復帰方法です。SendMessage ではメッセージを送った先のウィンドウの処理が完了するまで制御が戻りませんが、PostMessage では即座に制御が戻ります。
  
 
Here is an example of how a secondary thread could send text to be displayed in an LCL control to the main thread:
 
Here is an example of how a secondary thread could send text to be displayed in an LCL control to the main thread:
Line 490: Line 508:
  
 
== クリティカル・セクション ==
 
== クリティカル・セクション ==
 
 
''クリティカル・セクション''はオブジェクトの一つで、ある部分のコードがある時点では一つのスレッドだけで実行されること(排他的に実行されること)を保証するものです。クリティカル・セクションは使用前に作成/初期化し、使用後は解放する必要があります。
 
''クリティカル・セクション''はオブジェクトの一つで、ある部分のコードがある時点では一つのスレッドだけで実行されること(排他的に実行されること)を保証するものです。クリティカル・セクションは使用前に作成/初期化し、使用後は解放する必要があります。
  
 
通常の使い方です:
 
通常の使い方です:
  
セクションを宣言します。(そのセクションを実行する可能性のある全てのスレッドから見えるよう十分大域に宣言しなければなりません):
+
Declare the section (globally for all threads which should access the section):
 
   MyCriticalSection: TRTLCriticalSection;
 
   MyCriticalSection: TRTLCriticalSection;
  
Line 505: Line 522:
 
EnterCriticalSection(MyCriticalSection);
 
EnterCriticalSection(MyCriticalSection);
 
try
 
try
   // access some variables, write files, send some network packets, etc
+
   // 変数へのアクセス、ファイルの書き出し、ネットワークパケットの送信、など
 
finally
 
finally
 
   LeaveCriticalSection(MyCriticalSection);
 
   LeaveCriticalSection(MyCriticalSection);
Line 521: Line 538:
  
 
=== 変数の共用 ===
 
=== 変数の共用 ===
 
 
複数のスレッドが一つの変数を共用する場合、読み出すだけなら何も問題はありません。単に読めばいいのです。しかし、一つまたは複数のスレッドがその値を変更しようとするなら、複数のスレッドがその変数に同時にアクセスしないように注意しなければなりません。
 
複数のスレッドが一つの変数を共用する場合、読み出すだけなら何も問題はありません。単に読めばいいのです。しかし、一つまたは複数のスレッドがその値を変更しようとするなら、複数のスレッドがその変数に同時にアクセスしないように注意しなければなりません。
  
例: 五つのスレッドが一つのカウンタをインクリメントします。
+
例: 五つのスレッドが一つのカウンタをインクリメントします。lazarus/examples/multithreading/criticalsectionexample1.lpi 参照。
lazarus/examples/multithreading/criticalsectionexample1.lpi 参照。
 
  
 
== 別のスレッドの処理を待つ方法 ==
 
== 別のスレッドの処理を待つ方法 ==
 
 
スレッドAが別のスレッドBの結果を利用するなら、AはBの処理が終了するまで待つ必要があります。
 
スレッドAが別のスレッドBの結果を利用するなら、AはBの処理が終了するまで待つ必要があります。
  
Line 564: Line 578:
 
   end;
 
   end;
 
end;</syntaxhighlight>
 
end;</syntaxhighlight>
 +
 +
{{Note| RtlEventSetEvent can be called before RtlEventWaitFor. Then RtlEventWaitFor will return immediately. Use RTLeventResetEvent to clear a flag.}}
  
 
== Fork ==
 
== Fork ==
 
+
When forking in a multi-threaded application, be aware that any threads created and running BEFORE the fork (or fpFork) call, will NOT be running in the child process. As stated on the fork() man page, any threads that were running before the fork call, their state will be undefined.
When forking in a multithreaded application, be aware that any threads created and running BEFORE the fork (or fpFork) call, will NOT be running in the child process. As stated on the fork() man page, any threads that were running before the fork call, their state will be undefined.
 
  
 
So be aware of any threads initializing before the call (including on the initialization section). They will NOT work.
 
So be aware of any threads initializing before the call (including on the initialization section). They will NOT work.
  
 
== Parallel procedures/loops ==
 
== Parallel procedures/loops ==
 
 
A special case of multi threading is running a single procedure in parallel. See [[Parallel procedures]].
 
A special case of multi threading is running a single procedure in parallel. See [[Parallel procedures]].
  
== Distributed computing ==
+
==分散コンピューティング==
 
 
 
The next higher steps after multi threading is running the threads on multiple machines.  
 
The next higher steps after multi threading is running the threads on multiple machines.  
 
* You can use one of the TCP suites like synapse, lnet or indy for communications. This gives you maximum flexibility and is mostly used for loosely connected Client / Server applications.
 
* You can use one of the TCP suites like synapse, lnet or indy for communications. This gives you maximum flexibility and is mostly used for loosely connected Client / Server applications.
Line 583: Line 596:
  
  
== External threads ==
+
==外部スレッド==
To make Free Pascal's threading system to work properly, each newly created FPC thread needs to be initialized (more exactly, the exception, I/O system and threadvar system per thread needs to be initialized so threadvars and heap are working). That is fully automatically done for you if you use BeginThread (or indirectly by using the TThread class). However, if you use threads that were created without BeginThread (i.e. external threads), additional work (currently) might be required. External threads also include those that were created in external C libraries (.DLL/.so).   
+
To make Free Pascal's threading system work properly, each newly created FPC thread needs to be initialized (more exactly, the exception, I/O system and threadvar system per thread needs to be initialized so threadvars and heap are working). That is fully automatically done for you if you use BeginThread (or indirectly by using the TThread class). However, if you use threads that were created without BeginThread (i.e. external threads), additional work (currently) might be required. External threads also include those that were created in external C libraries (.DLL/.so).   
  
  
Line 593: Line 606:
 
If the calling convention doesn't fit (e.g. if your original thread function needs cdecl calling convention but BeginThread needs pascal convention, create a record, store the original required thread function in it, and call that function in your pascal thread function:   
 
If the calling convention doesn't fit (e.g. if your original thread function needs cdecl calling convention but BeginThread needs pascal convention, create a record, store the original required thread function in it, and call that function in your pascal thread function:   
  
<syntaxhighlight>type  
+
<syntaxhighlight>type
  TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;  
+
  TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;
  
  PCdeclThreadFuncData = ^TCdeclThreadFunc;  
+
  PCdeclThreadFuncData = ^TCdeclThreadFuncData;
 
  TCdeclThreadFuncData = record
 
  TCdeclThreadFuncData = record
 
   Func: TCdeclThreadFunc;  //cdecl function
 
   Func: TCdeclThreadFunc;  //cdecl function
 
   Data: Pointer;          //original data
 
   Data: Pointer;          //original data
  end;    
+
  end;
  
 
// The Pascal thread calls the cdecl function
 
// The Pascal thread calls the cdecl function
Line 606: Line 619:
 
var
 
var
 
   ThreadData: TCdeclThreadFuncData;
 
   ThreadData: TCdeclThreadFuncData;
begin  
+
begin
 
   ThreadData := PCdeclThreadFuncData(FuncData)^;
 
   ThreadData := PCdeclThreadFuncData(FuncData)^;
 
   Result := ptrint(ThreadData.Func(ThreadData.Data));
 
   Result := ptrint(ThreadData.Func(ThreadData.Data));
end;  
+
end;
  
 
procedure CreatePascalThread;
 
procedure CreatePascalThread;
 
var
 
var
   ThreadData: PCdeclThreadFunc;  
+
   ThreadData: PCdeclThreadFuncData;
begin  
+
begin
 
   New(ThreadData);
 
   New(ThreadData);
 
   // this is the desired cdecl thread function
 
   // this is the desired cdecl thread function
   ThreadData^.Func := func;  
+
   ThreadData^.Func := func;
   ThreadData^.Data := user_data;  
+
   ThreadData^.Data := user_data;
 
   // this creates the Pascal thread
 
   // this creates the Pascal thread
 
   BeginThread(@C2P_Translator, ThreadData );
 
   BeginThread(@C2P_Translator, ThreadData );
Line 680: Line 693:
 
ThreadSwitch()
 
ThreadSwitch()
  
'''Note:''' Do not use the Windows trick ''Sleep(0)'' as this won't work on all platforms.
+
{{Note| Do not use the Windows trick ''Sleep(0)'' as this won't work on all platforms.}}
  
==See also==
+
==関連項目==
  
* [[Streaming components]]
+
* [[Streaming components/ja|コンポーネントをストリームする]]
 
* [[Manager Worker Threads System]]
 
* [[Manager Worker Threads System]]
* [[Example of multi-threaded application: array of threads]]
+
* [[Example of multi-threaded application: array of threads|マルチスレッドアプリケーションの例:スレッドの配列]]
 
* [[Parallel procedures]]
 
* [[Parallel procedures]]
 +
* [[Main Loop Hooks]]
 +
* [[Asynchronous Calls/ja|非同期呼び出し]]
  
 
[[Category:Tutorials/ja]]
 
[[Category:Tutorials/ja]]
 
[[Category:Parallel programming/ja]]
 
[[Category:Parallel programming/ja]]
 +
[[Category:Multitasking/ja]]
 +
[[Category:FPC/ja]]

Revision as of 14:04, 27 December 2013

Deutsch (de) English (en) español (es) français (fr) 日本語 (ja) polski (pl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN)

日本語版メニュー
メインページ - Lazarus Documentation日本語版 - 翻訳ノート - 日本語障害情報

概要

このページでは Free Pascal と Lazarus を使ってマルチスレッドアプリケーションを書いたりデバッグしたりする方法について述べます。

もし、読者がマルチスレッディングに初めて触れるのなら、是非マルチスレッディングが本当に必要かどうかを「マルチスレッディングが必要ですか」節でお確かめください。そうすれば頭痛の種を激減することができるでしょう。

マルチスレッドアプリケーションは、2つかそれ以上のスレッドを作成、同時実行して作業を行うものです。そのうち一つは、メインスレッドと呼ばれるものです。メインスレッドはアプリケーションが起動する際、オペレーティングシステムによって生成されます。メインスレッドはユーザーインターフェイス、コンポーネントを更新する唯一のスレッドです。メインスレッドはかならずただ一つである必要があり、そうでないとアプリケーションは暴走します。

マルチスレッドプログラミングの基本的な考え方は、ユーザがメインスレッドを通して作業する傍ら、もう一つのスレッドを用いてある種の処理をバックグラウンドで行えるようにアプリケーションを組むという点です。

スレッドの他の使いかたとして、アプリケーションの応答速度を改善することが挙げられます。例えばあなたがアプリケーションを作り、ユーザーがボタンを押して、アプリケーションが処理を開始したとします(すごく重たい処理です)。処理の間、スクリーンは凍りつき反応が無くなり、ユーザーはアプリケーションが落ちたと思うでしょう。よくないことです。もし、その重たい処理を2つ目のスレッドの中で実行するようにすれば、アプリケーションの応答性は保たれ、まるでそんな処理は行っていないかのように振舞います。このような場合には、スレッドを開始する前にフォームの処理を開始するボタンを使用不可能にしておくと、ユーザーがそれ以上の仕事を要求することを無くすことができ、好都合です。

また、サーバアプリケーションを作る際に、同時に大量のクライアントに応答しなければならない場合にも使えます。

マルチスレッディングが必要ですか?

もし読者がマルチスレッディングにこれまで触れたことがなく、単に重い処理を実行するアプリケーションの応答性を改善したいだけなら、マルチスレッディングはお探しのものとは違うかもしれません。マルチスレッドアプリケーションはしばしば複雑であり、デバグは常に困難です。また、多くの場合マルチスレッディングは必要のないものです。単一スレッドアプリケーションで十分です。時間のかかるタスクを細かな部分に分割することができるなら、マルチスレッディングの代わりに Application.ProcessMessages を使うべきです。このメソッドは LCL に待機中のメッセージを全て処理させるものです。分割したタスクの一部分を実行したら Application.ProcessMessages を呼び、ユーザがアプリケーションを終了させたりどこかをクリックしたりしたかを確認し、あるいは進行状況表示を再描画したりします。その後タスクの次の部分を実行し、また Application.ProcessMessages を呼びます。

例: 大きなファイルの読み込みと処理。examples/multithreading/singlethreadingexample1.lpi 参照。

マルチスレッディングが必要なのは、次のような場合だけです -

  • 同期型のネットワーク通信のような、ブロッキングタイプの処理の場合(訳者注:同期型の場合タイムアウトするまで処理が戻ってこない可能性がありますから、例えば別スレッドでキャンセル待ち処理をするような使い方が考えられます)。
  • マルチプロセッサによる同時処理(SMP)の場合。
  • APIを呼び出さなければならないためにそれ以上細かく分割できないアルゴリズムやライブラリの呼び出し(訳者注:そのような分割できない長い処理を普通に行うと、ハングアップして固まったかのように見えてしまいますから、処理時間が長くかかる処理は別スレッドで処理して、描画やウィンドウの移動などの操作にはメインスレッドで反応するような使い方が考えられます)。

If you want to use multi-threading to increase speed by using multiple processors simultaneously, check if your current program now use all 100% resources of 1 core CPU (for example, you program can actively use input-output operations, e.g. writing to file; this takes a lot of time, but doesn't load CPU; in this case you program will not be faster with multiple threads). Also check if optimisation level is set to maximum (3). When I switched optimisation level from 1 to 3, my program became about 5 times faster.

マルチスレッドアプリケーションで必要となるユニット

You don´t need any special unit for this to work with Windows. However with Linux, Mac OS X and FreeBSD, you need the cthreads unit and it must be the first used unit of the project (the program source, usually the .lpr file)!

So, your Lazarus application code should look like:

program MyMultiThreadedProgram;
{$mode objfpc}{$H+}
uses
{$ifdef unix}
  cthreads,
  cmem, // the c memory manager is on some systems much faster for multi-threading
{$endif}
  Interfaces, // これには LCL ウィジェットセットが含まれています
  Forms
  { お使いになられているユニットをここに追加します },

If you forget this and you use TThread you will get this error on startup:

 This binary has no thread support compiled in.
 Recompile the application with a thread-driver in the program uses clause before other units using thread.
Light bulb  Note: If you get a Linker error about "mcount" not found. Then you use some unit that contains some multithreaded code and you need to add the cthreads unit or use smart linking.
Light bulb  Note: If you get the error: "Project raised exception class 'RunError(232)'" in procedure SYSTEM_NOTHREADERROR then your code requires threading and you need to add the cthreads unit.

TThreadクラス

このサンプルは「examples/multithreading/」にあります。

マルチスレッドアプリケーションはTThreadクラスを使用すると簡単に作成できます。このクラスを用いると追加のスレッド(メインスレッドと並列に実行する)を作ることができます。

そのためには通常、Create コンストラクタと、Execute メソッドの二つのメソッドをオーバーライドしなければなりません。

コンストラクタは、あなたが走らせるスレッドを準備する際に使います。あなたは変数もしくはプロパティに適切な初期値を納める必要があります。TThread のオリジナルのコンストラクタは Suspended というパラメータを必要とします。名前からもおわかりのように、Suspended が False の場合、スレッドは作成後直ちに実行されます。逆に Suspended が True の場合、スレッドは停止状態で作成されます。この場合、スレッドを走らせるには Start メソッドを呼びます。

Light bulb  Note: メソッド Resume は FPC 2.4.4 以降非推奨になりました。Resume は Start に置き換えてください。

FPCのバージョンが2.0.1またはそれ以降の場合、TThread.Create は StackSize という暗黙のパラメータを持っています。あなたは必要ならば自分の作るスレッドのデフォルトスタックサイズを変更することが可能です。 例えばスレッド内で深い再帰呼び出しを行う際には重宝するでしょう。あなたがスタックサイズを指定しなければ、OSによる規定のスタックサイズが使用されます。

スレッドで実行したいコードは、Execute メソッドをオーバーライドして、その中に書きます。

TThread クラスは一つの重要なプロパティを持っています。それは:

Terminated : boolean;

です。

Terminated のデフォルト値は False です。通常の場合、スレッドはループを持っていますが、Terminated が True になったら、そのループから脱出しなければなりません。したがってループのサイクル毎に、Terminatedが True になっていないかチェックする必要があります。もし True になっていたなら、速やかに必要なクリーンアップを行い、Execute メソッドを終了させなければなりません。

As we explained earlier, the thread should not interact with the visible components. Updates to visible components must be made within the context of the main thread.

To do this, a TThread method called Synchronize exists. Synchronize requires a method within the thread (that takes no parameters) as an argument. When you call that method through Synchronize(@MyMethod), the thread execution will be paused, the code of MyMethod will be called from the main thread, and then the thread execution will be resumed.

The exact working of Synchronize depends on the platform, but basically it does this:

  • it posts a message onto the main message queue and goes to sleep
  • eventually the main thread processes the message and calls MyMethod. This way MyMethod is called without context, that means not during a mouse down event or during paint event, but after.
  • after the main thread executed MyMethod, it wakes the sleeping Thread and processes the next message
  • the Thread then continues.

There is another important property of TThread: FreeOnTerminate. If this property is true, the thread object is automatically freed when the thread execution (.Execute method) stops. Otherwise the application will need to free it manually.

例:

  Type
    TMyThread = class(TThread)
    private
      fStatusText : string;
      procedure ShowStatus;
    protected
      procedure Execute; override;
    public
      Constructor Create(CreateSuspended : boolean);
    end;

  constructor TMyThread.Create(CreateSuspended : boolean);
  begin
    FreeOnTerminate := True;
    inherited Create(CreateSuspended);
  end;

  procedure TMyThread.ShowStatus;
  // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
  begin
    Form1.Caption := fStatusText;
  end;
 
  procedure TMyThread.Execute;
  var
    newStatus : string;
  begin
    fStatusText := 'TMyThread Starting...';
    Synchronize(@Showstatus);
    fStatusText := 'TMyThread Running...';
    while (not Terminated) and ([他のループ終了条件]) do
      begin
        ...
        [ここにスレッドループの本体コードを記述します]
        ...
        if NewStatus <> fStatusText then
          begin
            fStatusText := newStatus;
            Synchronize(@Showstatus);
          end;
      end;
  end;

アプリケーション側では,

  var
    MyThread : TMyThread;
  begin
    MyThread := TMyThread.Create(True); // このようにすると自動的に開始しません
    ...
    [ここにスレッドを実行する前に必要な初期化コードを記述します]
    ...
    MyThread.Start;
  end;

もっと柔軟な制御を望むなら、スレッドに対しイベントを発行することができ - そうすると、sychronize されたメソッドは特定のフォームやクラスに縛られなくなります - そのイベントに対するリスナーを設定することができます。次の例で、TMyThread.ShowStatus はメインスレッドで実行されるので、すべての GUI 要素に触ることができます:

  Type
    TShowStatusEvent = procedure(Status: String) of Object;

    TMyThread = class(TThread)
    private
      fStatusText : string;
      FOnShowStatus: TShowStatusEvent;
      procedure ShowStatus;
    protected
      procedure Execute; override;
    public
      Constructor Create(CreateSuspended : boolean);
      property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
    end;

  constructor TMyThread.Create(CreateSuspended : boolean);
  begin
    FreeOnTerminate := True;
    inherited Create(CreateSuspended);
  end;

  procedure TMyThread.ShowStatus;
  // このメソッドはメインスレッドで実行されるので、すべての GUI 要素にアクセス可能です。
  begin
    if Assigned(FOnShowStatus) then
    begin
      FOnShowStatus(fStatusText);
    end;
  end;

  procedure TMyThread.Execute;
  var
    newStatus : string;
  begin
    fStatusText := 'TMyThread Starting...';
    Synchronize(@Showstatus);
    fStatusText := 'TMyThread Running...';
    while (not Terminated) and ([他のループ終了条件]) do
      begin
        ...
        [ここにスレッドループの本体コードを記述します]
        ...
        if NewStatus <> fStatusText then
          begin
            fStatusText := newStatus;
            Synchronize(@Showstatus);
          end;
      end;
  end;

アプリケーション側では,

  Type
    TForm1 = class(TForm)
      Button1: TButton;
      Label1: TLabel;
      procedure FormCreate(Sender: TObject);
      procedure FormDestroy(Sender: TObject);
    private
      { private declarations }
      MyThread: TMyThread; 
      procedure ShowStatus(Status: string);
    public
      { public declarations }
    end;

  procedure TForm1.FormCreate(Sender: TObject);
  begin
    inherited;
    MyThread := TMyThread.Create(true);
    MyThread.OnShowStatus := @ShowStatus;
  end;

  procedure TForm1.FormDestroy(Sender: TObject);
  begin
    MyThread.Terminate;

    // FreeOnTerminate is true so we should not write:
    // MyThread.Free;
    inherited;
  end;

  procedure TForm1.Button1Click(Sender: TObject);
  begin
   MyThread.Start;
  end;

  procedure TForm1.ShowStatus(Status: string);
  begin
    Label1.Caption := Status;
  end;

(日本語訳注: FreeOnTerminate = True で使用した場合に厄介なのはメイン側からスレッドを制御するのが非常に困難になることです。例えば上のコード例で、FormDestroy や Button1Click が呼ばれる前に MyThread.Execute が終了してしまって MyThread が自動解放された場合、正常に動作するのか疑問が残ります。FreeOnTerminate = False のままで使用して、スレッドをメインスレッドで確実に制御するやり方のほうがより実用的でバグもでにくいのでお勧めです。スレッドが止まってもメモリが解放されない点がデメリットといえばデメリットですが、工夫によりさほど気にならないメモリ消費量で済むのではないでしょうか。

FreeOnTerminate = False とした場合の例:

  Type
    TMyThread = class(TThread)
    private
      fStatusText : string;
      procedure ShowStatus;
    protected
      procedure Execute; override;
    public
      Constructor Create(CreateSuspended : boolean);
    end;

  constructor TMyThread.Create(CreateSuspended : boolean);
  begin
    FreeOnTerminate := False;
    inherited Create(CreateSuspended);
  end;

  procedure TMyThread.ShowStatus;
  // このメソッドはメインスレッドで実行されるので GUI 要素にアクセスすることができます。
  // また、共有変数への書き込みもこのメソッドでやればうまく動作します。
  begin
    Form1.Caption := fStatusText;
  end;
 
  procedure TMyThread.Execute;
  var
    newStatus : string;
  begin
    try
      fStatusText := 'TMyThread Starting...';
      Synchronize(@Showstatus);
      fStatusText := 'TMyThread Running...';
      while (not Terminated) and ([他のループ終了条件]) do
        begin
          ...
          [here goes the code of the main thread loop]
          ...
          if NewStatus <> fStatusText then
            begin
              fStatusText := newStatus;
              Synchronize(@Showstatus);
            end;
        end;
    finally
      fStatusText := ''; // メモリを節約するため空に
    end;
  end;
  
  ...
  //--- アプリケーション側 --------------------------------------
  ...

 TForm1 = class(TForm)
   ...
   MyThread : TMyThread;
   ...
 end;

 procedure TForm1.FormCreate(Sender: TObject);
 begin
   MyThread := nil;
 end;
 
 procedure TForm1.FormDestroy(Sender: TObject);
 begin
   if Assigned(MyThread) then MyThread.Free;
 end;
 
 procedure TForm1.Button1Click(Sender: TObject);
 begin
   if Assigned(MyThread) then
   begin
     MyThread.Terminate; 
     MyThread.WaitFor; 
     MyThread.Free; 
     MyThread := nil;
   end;
 
   MyThread := TMyThread.Create(True); // 再作成
   ...
   [Here the code initialises anything required before the threads starts executing]
   ...
   MyThread.Resume; // 再スタート
 end;

日本語訳注終わり)

特別に注意すべきこと

Windows におけるスタックチェック

Windowsで-Ctスイッチ(スタックチェック)をつかうスレッドには頭の痛い問題があります。理由は定かではありませんが、デフォルトのスタックサイズを使う場合、スタックチェックが TTread.Create をトリガーしてしまうことがあります。現在の運用上の回避策としては -Ctスイッチを使わないことです。これがメインスレッドで例外をひきおこすことは全くなく、新しく生成されたスレッドで起こります。そのためスレッドが絶対開始できないように見えます。

スレッド生成時に発生するその他の例外や、この問題をチェックする良いコードを示します。

MyThread := TThread.Create(False);
if Assigned(MyThread.FatalException) then
  raise MyThread.FatalException;

このコードはスレッド生成時に発生するどんな例外でも、メインのスレッドでraiseするようにします。

Multithreading in packages

Packages which uses multi-threading should add the -dUseCThreads flag to the custom usage options. Open the package editor of the package, then Options > Usage > Custom and add -dUseCThreads. This will define this flag to all projects and packages using this package, including the IDE. The IDE and all new applications created by the IDE have already the following code in their .lpr file:

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  cmem, // C のメモリマネージャは、特定のシステムのマルチスレッドにおいてとても高速に動作します
  {$ENDIF}{$ENDIF}

Heaptrc

cmem ユニットを使った上で -gh スイッチを使うことはできません。-gh スイッチは、(メモリーリークを調べるために)ヒープマネージャーを拡張する heaptrc ユニットを使用するものです。ですから(リークチェックしたいのであれば)、cmem ユニットを使ってから その後に heaptrc ユニットを置くようにする必要があります。(訳注:heaptrc は cmem を考慮するように作られているけど、cmem はそうではないからでしょう。)

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  cmem, // C のメモリマネージャは、特定のシステムのマルチスレッドにおいてとても高速に動作します
  {$ENDIF}{$ENDIF}
  heaptrc,

SMP(対称型マルチプロセッサCPU)のサポート

良いニュースです。この用法でマルチスレッドでアプリケーションとして正しく動作すれば、それはSMPで並列動作が有効になります。

Lazarus でのマルチスレッドアプリケーションのデバッグ

The debugging on Lazarus requires GDB and is rapidly becoming more and more fully featured and stable. However, there still exists a few Linux distributions with some problems.

デバッグ出力

In a single threaded application, you can simply write to console/terminal/whatever and the order of the lines is the same as they were written. In multi-threaded application things are more complicated. If two threads are writing, say a line is written by thread A before a line by thread B, then the lines are not necessarily written in that order. It can even happen, that a thread writes its output, while the other thread is writing a line.While under linux (maybe) you'll get proper DebugLn() output, under win32 you can get exceptions (probably DiskFull) because of DebugLn() usage outside of main thread.So, to avoid headaches use DebugLnThreadLog() mentioned below.

The LCLProc unit contains several functions, to let each thread write to its own log file:

  procedure DbgOutThreadLog(const Msg: string); overload;
  procedure DebuglnThreadLog(const Msg: string); overload;
  procedure DebuglnThreadLog(Args: array of const); overload;
  procedure DebuglnThreadLog; overload;

例: writeln('Some text ',123); の代わりに、これを使います

 DebuglnThreadLog(['Some text ',123]);

こうすると、 'Some text 123' という行が Log<PID>.txt に追加されます。ここで <PID> はそのスレッドのプロセスIDです。

実行する前毎に、ログファイルを削除するとよいでしょう:

 rm -f Log* && ./project1

Linux

If you try to debug a multi-threaded application on Linux, you will have one big problem: the Desktop Manager on X server can hang. This happens for instance when the application has captured the mouse/keyboard and was paused by gdb and the X server waits for your application. When that happens you can simply log in from another computer and kill the gdb or exit out of that session by pressing CTRL+ALT+F3 and kill gdb. Alternatively you can restart the window manager: enter sudo /etc/init.d/gdm restart. This will restart the desktop manager and get you back into your desktop.

Since it depends where gdb stops your program in some cases some tricks may help: for Ubuntu x64 set the Project options for debugging required extra information file...

 プロジェクトオプション → コンパイラオプション → リンク → デバッグ:外部の gdb デバッグシンボルファイルを使用 (-Xg) をチェックします。

The other option is to open anotner X desktop, run the IDE/gdb on one and the application on the other, so that only the test desktop freezes. Create a new instance of X with:

 X :1 &

It will open, and when you switch to another desktop (the one you are working with pressing CTRL+ALT+F7), you will be able to go back to the new graphical desktop with CTRL+ALT+F8 (if this combination does not work, try with CTRL+ALT+F2... this one worked on Slackware).

Then you could, if you want, create a desktop session on the X started with:

 gnome-session --display=:1 &

そしてLazarusでプロジェクトの run parameters dialogで"Use display"をチェックして、:1を入力します。

これでアプリケーションは2番目のXサーバーで動作するようになり、最初のXサーバーでデバッグが可能になりました。

この方法は、FPC2.0とLazarus 0.9.10で、OSはWindowsとLinuxでテストしました。



新しい X セッションを開始する代わりに、 Xnest を用いることもできます。Xnest は X セッションをウィンドウで開くものです。これを用いると、スレッドをデバグする間も X サーバがロックしません。また、複数のターミナルを行ったり来たりしながらデバグするよりも楽です。

Xnestを起動するには、次のようなコマンドをタイプします:

 Xnest :1 -ac

一つの X セッションが :1 で開き、アクセス制御がディセーブルされます。

Lazarus Widgetset Interfaces

The win32, the gtk and the carbon interfaces support multi-threading. This means, TThread, critical sections and Synchronize work. But they are not thread safe. This means only one thread at a time can access the LCL. And since the main thread should never wait for another thread, it means only the main thread is allowed to access the LCL, which means anything that has to do with TControl, Application and LCL widget handles. There are some thread safe functions in the LCL. For example most of the functions in the FileUtil unit are thread safe.

スレッド間のやり取りに SendMessage/PostMessage を使用する

Only one thread in an application should call LCL APIs, usually the main thread. Other threads can make use of the LCL through a number of indirect methods, one good option being the usage of SendMessage or PostMessage. LCLIntf.SendMessage and LCLIntf.PostMessage will post a message directed to a window in the message pool of the application.

これらのルーチンのドキュメントもお読みください。

SendMessage と PostMessage の違いは、呼び出した側への制御の復帰方法です。SendMessage ではメッセージを送った先のウィンドウの処理が完了するまで制御が戻りませんが、PostMessage では即座に制御が戻ります。

Here is an example of how a secondary thread could send text to be displayed in an LCL control to the main thread:

const
  WM_GOT_ERROR           = LM_USER + 2004;
  WM_VERBOSE             = LM_USER + 2005;

procedure VerboseLog(Msg: string);
var
  PError: PChar;
begin
  if MessageHandler = 0 then Exit;
  PError := StrAlloc(Length(Msg)+1);
  StrCopy(PError, PChar(Msg));
  PostMessage(formConsole.Handle, WM_VERBOSE, Integer(PError), 0);
end;

And an example of how to handle this message from a window:

const
  WM_GOT_ERROR           = LM_USER + 2004;
  WM_VERBOSE             = LM_USER + 2005;

type
  { TformConsole }

  TformConsole = class(TForm)
    DebugList: TListView;
    // ...
  private
    procedure HandleDebug(var Msg: TLMessage); message WM_VERBOSE;
  end;

var
  formConsole: TformConsole;

implementation

....

{ TformConsole }

procedure TformConsole.HandleDebug(var Msg: TLMessage);
var
  Item: TListItem;
  MsgStr: PChar;
  MsgPasStr: string;
begin
  MsgStr := PChar(Msg.wparam);
  MsgPasStr := StrPas(MsgStr);
  Item := DebugList.Items.Add;
  Item.Caption := TimeToStr(SysUtils.Now);
  Item.SubItems.Add(MsgPasStr);
  Item.MakeVisible(False);
  //f/TrayControl.SetError(MsgPasStr);
end;

end.

クリティカル・セクション

クリティカル・セクションはオブジェクトの一つで、ある部分のコードがある時点では一つのスレッドだけで実行されること(排他的に実行されること)を保証するものです。クリティカル・セクションは使用前に作成/初期化し、使用後は解放する必要があります。

通常の使い方です:

Declare the section (globally for all threads which should access the section):

 MyCriticalSection: TRTLCriticalSection;

セクションを作成します:

 InitializeCriticalSection(MyCriticalSection);

いくつかのスレッドを走らせます。ここで排他的になにかをするには次のようにします:

EnterCriticalSection(MyCriticalSection);
try
  // 変数へのアクセス、ファイルの書き出し、ネットワークパケットの送信、など
finally
  LeaveCriticalSection(MyCriticalSection);
end;

全てのスレッドが停止した後、セクションを解放します:

 DeleteCriticalSection(MyCriticalSection);

別の方法として、TCriticalSection オブジェクトも使えます。オプジェクトを作成するとセクションの作成と初期化を、Enter メソッドを実行すると EnterCriticalSection と同様の動作を、Leave メソッドを実行すると LeaveCriticalSection と同様の動作を、オブジェクトを破棄するとセクションの削除を行います。

For example: 5 threads incrementing a counter. See lazarus/examples/multithreading/criticalsectionexample1.lpi

注意: 上記の4つの手続きは RTL(FPCのランタイム)と LCL に一組づつ存在します。RTL のは SyncObjs ユニットにありますが、LCL のは LCLIntf ユニットと LCLType で定義されています。どちらの組も同様に用いることができます。アプリケーションの中で混用することもできますが、RTL の関数/手続きには RTL のクリティカルセクションを、LCL の関数/手続きには LCL のクリティカルセクションを使わなければなりません。

変数の共用

複数のスレッドが一つの変数を共用する場合、読み出すだけなら何も問題はありません。単に読めばいいのです。しかし、一つまたは複数のスレッドがその値を変更しようとするなら、複数のスレッドがその変数に同時にアクセスしないように注意しなければなりません。

例: 五つのスレッドが一つのカウンタをインクリメントします。lazarus/examples/multithreading/criticalsectionexample1.lpi 参照。

別のスレッドの処理を待つ方法

スレッドAが別のスレッドBの結果を利用するなら、AはBの処理が終了するまで待つ必要があります。

重要: メインスレッドは決して他のスレッドの処理を待ってはいけません。その代わりに上記の Synchronize を用います。

参照すべき例: lazarus/examples/multithreading/waitforexample1.lpi

{ TThreadA }

procedure TThreadA.Execute;
begin
  Form1.ThreadB:=TThreadB.Create(false);
  // create event
  WaitForB:=RTLEventCreate;
  while not Application.Terminated do begin
    // wait infinitely (until B wakes A)
    RtlEventWaitFor(WaitForB);
    writeln('A: ThreadB.Counter='+IntToStr(Form1.ThreadB.Counter));
  end;
end;

{ TThreadB }

procedure TThreadB.Execute;
var
  i: Integer;
begin
  Counter:=0;
  while not Application.Terminated do begin
    // B: Working ...
    Sleep(1500);
    inc(Counter);
    // wake A
    RtlEventSetEvent(Form1.ThreadA.WaitForB);
  end;
end;
Light bulb  Note: RtlEventSetEvent can be called before RtlEventWaitFor. Then RtlEventWaitFor will return immediately. Use RTLeventResetEvent to clear a flag.

Fork

When forking in a multi-threaded application, be aware that any threads created and running BEFORE the fork (or fpFork) call, will NOT be running in the child process. As stated on the fork() man page, any threads that were running before the fork call, their state will be undefined.

So be aware of any threads initializing before the call (including on the initialization section). They will NOT work.

Parallel procedures/loops

A special case of multi threading is running a single procedure in parallel. See Parallel procedures.

分散コンピューティング

The next higher steps after multi threading is running the threads on multiple machines.

  • You can use one of the TCP suites like synapse, lnet or indy for communications. This gives you maximum flexibility and is mostly used for loosely connected Client / Server applications.
  • You can use message passing libraries like MPICH, which are used for HPC (High Performance Computing) on clusters.


外部スレッド

To make Free Pascal's threading system work properly, each newly created FPC thread needs to be initialized (more exactly, the exception, I/O system and threadvar system per thread needs to be initialized so threadvars and heap are working). That is fully automatically done for you if you use BeginThread (or indirectly by using the TThread class). However, if you use threads that were created without BeginThread (i.e. external threads), additional work (currently) might be required. External threads also include those that were created in external C libraries (.DLL/.so).


Things to consider when using external threads (might not be needed in all or future compiler versions):

  • Do not use external threads at all - use FPC threads. If can you can get control over how the thread is created, create the thread by yourself by using BeginThread.

If the calling convention doesn't fit (e.g. if your original thread function needs cdecl calling convention but BeginThread needs pascal convention, create a record, store the original required thread function in it, and call that function in your pascal thread function:

type
 TCdeclThreadFunc = function (user_data:Pointer):Pointer;cdecl;

 PCdeclThreadFuncData = ^TCdeclThreadFuncData;
 TCdeclThreadFuncData = record
   Func: TCdeclThreadFunc;  //cdecl function
   Data: Pointer;           //original data
 end;

// The Pascal thread calls the cdecl function
function C2P_Translator(FuncData: pointer) : ptrint;
var
  ThreadData: TCdeclThreadFuncData;
begin
  ThreadData := PCdeclThreadFuncData(FuncData)^;
  Result := ptrint(ThreadData.Func(ThreadData.Data));
end;

procedure CreatePascalThread;
var
  ThreadData: PCdeclThreadFuncData;
begin
  New(ThreadData);
  // this is the desired cdecl thread function
  ThreadData^.Func := func;
  ThreadData^.Data := user_data;
  // this creates the Pascal thread
  BeginThread(@C2P_Translator, ThreadData );
end;


  • Initialize the FPC's threading system by creating a dummy thread. If you don't create any Pascal thread in your app, the thread system won't be initialized (and thus threadvars won't work and thus heap will not work correctly).
type
   tc = class(tthread)
     procedure execute;override;
   end;

   procedure tc.execute;
   begin
   end;

{ main program } 
begin
  { initialise threading system }
   with tc.create(false) do
   begin
     waitfor;
     free;
   end;
   { ... your code follows } 
end.

(After the threading system is initialized, the runtime may set the system variable "IsMultiThread" to true which is used by FPC routines to perform locks here and there. You should not set this variable manually.)


  • If for some reason this doesn't work for you, try this code in your external thread function:
function ExternalThread(param: Pointer): LongInt; stdcall;
var
  tm: TThreadManager;
begin
  GetThreadManager(tm);
  tm.AllocateThreadVars;
  InitThread(1000000); // adjust inital stack size here
  
  { do something threaded here ... }
    
  Result:=0;
end;


Identifying external threads

Sometimes you even don't know if you have to deal with external threads (e.g. if some C library makes a callback). This can help to analyse this:

1. Ask the OS for the ID of the current thread at your application's start

Win32: GetCurrentThreadID();
Darwin: GetThreadID();  
Linux: TThreadID(pthread_self);

2. Ask again for the ID of the current thread inside the thread function and compare this by the result of step 1.

Give up some time slice

ThreadSwitch()

Light bulb  Note: Do not use the Windows trick Sleep(0) as this won't work on all platforms.

関連項目