第22講 素数探索を題材としたマルチスレッドプログラミングの学習
第5話 スレッドの生成

グループ分けはできました。では各グループを担当するスレッドを生成するにはどうしたらよいでしょうか。
並列処理をイメージして頂くための前に示した図を少し訂正します。

スレッドAやスレッドBを派生しても、ルートスレッドSystem::Void button1_Click(System::Object^ sender, System::EventArgs^ e)はそのまま生き続けます。

マルチスレッドにするために

namespace ソフト名 {

  using namespace System;
  using namespace System::ComponentModel;
  using namespace System::Collections;
  using namespace System::Windows::Forms;
  using namespace System::Data;
  using namespace System::Drawing;
  using namespace System::Threading;

というようにusing namespace System::Threading;を加えてください。

そしてスレッドを生成するには、
Thread^ 変数名=gcnew Thread(gcnew ThreadStart(関数名));
を使います。それぞれのスレッドをスタートさせるには、
変数名−>Start();
とします。
例えば、
Thread^ A=gcnew Thread(gcnew ThreadStart(f1));
Thread^ B=gcnew Thread(gcnew ThreadStart(f2));
Thread^ C=gcnew Thread(gcnew ThreadStart(f3));
Thread^ D=gcnew Thread(gcnew ThreadStart(f4));

A−>Start();
B−>Start();
C−>Start();
D−>Start();
各スレッドA,B,C,Dがスタートします。
関数f1と変数Aの両方が出て来て、紛らわしいと感じるでしょうが、
f1という関数をスレッドAの上で動かしなさい、ということなのです。
マルチスレッド使うときに、スレッドの終了を待つ
変数名−>Join();
が必要です。上の例の即して書くと、
A−>Join();
B−>Join();
C−>Join();
D−>Join();
を加える必要があります。
どうしてこれが必要かと言いますと,
各スレッドが終了するタイミングは同時ではありません。
(以下の説明は、冒頭のイメージ図を参照してお読みください。)
例えば、スレッドAのみが終了しているのに、他のスレッドB,C,Dの終了を待たずに、
ルートスレッドを進めるとおかしなことになってしまします。
今回の例では、
T 5で割ると1余る奇数
U 5で割ると2余る奇数
V 5で割ると3余る奇数
W 5で割ると4余る奇数
4グループに分け、AがT、BがU、CがV、DがWを担当しています。
スレッドB,C,Dの終了を待たないで、ルートを進めてしまうと、
UVWの仲間が落とされてしまうことになります。
だから、すべての派生スレッドが終了するまでルートスレッドは待機していなければならないわけです。
その待機の命令が、
変数名−>Join();
なのです。

さらに、マルチスレッドを使うときには、各関数は静的関数にしなければならないと言うことです。
具体的には、
static void f1()とstaticを付けなければなりません。
静的関数=static関数とは、何時でもそこにある関数でるととりあえずは、理解してください。
普通の関数は、終了と同時に消滅してしまいます。
普通の関数は動的です。
消滅したり、生成したりを繰り返します。
それに対して静的関数は、スレッドAが消滅しても、残り続ける=静止している関数が、
静的関数だと思って頂いて結構かと思います。
普通の関数は、仕事の終了と同時に消滅しますが、
静的関数は、仕事が終わっても消滅しないで、次の仕事が来るまで待機している、
といったイメージでしょうか。


以上の説明を聞いても、いきなりでは前のシングルスレッド版の素数探索をマルチスレッド化せよと言われても、
何をやればいいのか皆目見当がつかないと思いますので、
マルチスレッドのプログラミングの具体例を示します。
1+2+3+4+5+・・・+10000という計算も実は、マルチスレッド対応プログラミングが可能です。
シングルでやる場合、
long i,w=0;
for(i=1;i=<10000)w+=i;
ですね。
この処理は、
例えば,
次の4つの処理に分割できます。
long i;
long w=0;
for(i=1;i=<2500;i++)w+=i;

long w2=0;
for(i=2501;i=<5000;i++w+=i;

long w2=0;
for(i=5001;i=<7500;i++)w+=i;

long w2=0;
for(i=7501;i=<10000;i++)w+=i;

label1->Text=(w1+w2+w3+w4).ToString();

色のついているところを各スレッドに分担させれば、
計算効率は高くなります。
4つのスレッドで同時に計算をしているからです。

実際にプログラミングするときは、次のようにしましょう。
long i;
n=long::Parse(textBox1->Text);
long w1=0;
for(i=1;i=<n/4;i++)w1+=i;

long w2=0;
for(i=n/4+1;i=<n/2;i++)w2+=i;

long w2=0;
for(i=n/2+1;i=<3*(n/4);i++)w3+=i;

long w2=0;
for(i=3*(n/4)+1;i=<n;i++)w4+=i;

label1->Text=(w1+w2+w3+w4).ToString();
(nには100の倍数を入れることを前提)

では具体的にプログラムを組んでみましょう。
TextBoxに大きな値を入れて、タスクマネージャーでCPU使用率を見てみましょう。

プログラム例
pragma once
long w1,w2,w3,w4;
long n;
namespace 足し算の和(マルチ4) {

  using namespace System;
  using namespace System::ComponentModel;
  using namespace System::Collections;
  using namespace System::Windows::Forms;
  using namespace System::Data;
  using namespace System::Drawing;
  using namespace System::Threading;
          ・
          ・
          ・
#pragma endregion
  private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {
          n=long::Parse(textBox1->Text);
          w1=0,w2=0,w3=0,w4=0;
          Thread^ A=gcnew Thread(gcnew ThreadStart(f1));
          Thread^ B=gcnew Thread(gcnew ThreadStart(f2));
          Thread^ C=gcnew Thread(gcnew ThreadStart(f3));
          Thread^ D=gcnew Thread(gcnew ThreadStart(f4));

          A->Start();
          B->Start();
          C->Start();
          D->Start();

          A->Join();
          B->Join();
          C->Join();
          D->Join();

          label3->Text=(w1+w2+w3+w4).ToString();
       }
       static void f1(){
         long i;
         for(i=1;i<=n/4;i++)w1+=i;
       }
       static void f2(){
         long i;
         for(i=n/4+1;i<=n/2;i++)w2+=i;
       }
       static void f3(){
         long i;
         for(i=n/2+1;i<=3*(n/4);i++)w3+=i;
       }
       static void f4(){
         long i;
         for(i=3*(n/4)+1;i<=n;i++)w4+=i;
       }

  };
}

ダウンロード用ファイルForm7.h


では皆さん、タスクマネージャーを起動しておいて、パフォーマンスタブにしておいてから、
ビルドしTextBoxに大きな値を入れてCPU使用率を見てみてください。

見事にCPU100%を実現していることが分かります。

尚、CPU使用率をみるために大きな値を入れたときは、


のように正確な値は出ません。おそらく、.ToString();による変換がint型に限定されているためのようです。
解決方法を知っている方は、教えて頂ければと思います。

では、皆さん課題を2つ出してこの話を閉じます。
1つ目は、和を求めるソフトで時間計測ができるようにして、シングルの場合との速度差が分かるようにして下さい。
2つ目は、素数探索シングルスレッドをマルチ化して下さい。
素数の並び替えのついては、本日(2011/01/09)第12講第11話をアップしましたのでそれを参考にされて下さい。

第22講第4話へ 第22講第6話へ



VC++講義第1部へ
vb講義へ
VB講義基礎へ
初心者のための世界で一番わかりやすいVisual C++入門基礎講座
初心者のための世界で一番わかりやすいVisual Basic入門基礎講座