書籍転載
文法からはじめるプログラミング言語Microsoft Visual C++入門

並列処理を行うための基礎知識(Visual C++)
――第13章 並列処理〜マルチスレッドプログラミング(後編)――

WINGSプロジェクト 矢吹 太朗(監修 山田 祥寛)
2010/04/28
Page1 Page2 Page3 Page4

13.4 .NETにおけるマルチスレッド

 .NETにもBoostと同様なスレッドのためのクラスSystem::Threading::Threadが用意されています。この節では、このクラスの使い方を紹介します。

13.4.1 System::Threading::Thread

 スレッドのための関数を宣言しておいて、次の構文でThreadを生成します。

Thread^ 変数名 = gcnew Thread(gcnew ThreadStart(関数名));
[構文]スレッドの生成

 2から100までの素数を2つのスレッドを使って表示するプログラムは次のようになります。Boostのthreadと違って、ThreadオブジェクトはメソッドStart()によって明示的に開始させます。

#include "number.h"
using namespace System;
using namespace System::Threading;

const int N=100;

//3の倍数+1となる数だけを調べる
void threadFuncA()
{
  for (int n=4; n<=N; n+=3) {
    if (isPrime(n)) Console::Write("{0} ", n);
  }
}

//3の倍数+2となる数だけを調べる
void threadFuncB()
{
  for (int n=5; n<=N; n+=3) {
    if (isPrime(n)) Console::Write("{0} ", n);
  }
}

int main()
{
  Console::Write("2 3 ");
  //スレッドの生成
  Thread^ threadA=gcnew Thread(gcnew ThreadStart(threadFuncA));
  Thread^ threadB=gcnew Thread(gcnew ThreadStart(threadFuncB));

  threadA->Start(); //計算開始
  threadB->Start();

  threadA->Join();
  threadB->Join();
}
[サンプル]13-cli-primes1.cpp

 共通言語ランタイムサポートを有効にして実行すると、結果は次のようになります。

2 3 7 13 19 31 37 43 61 67 73 79 5 11 17 23 29 41 47 53 59 97 71 83 89

 オブジェクトcoutと異なり、Consoleを利用する際に排他制御をする必要はありません。Consoleは複数のスレッドから利用されることを想定して作られているからです。このように作られたものはスレッドセーフだと言われます。Consoleがスレッドセーフであることは、MSDNライブラリで確認できます*3

*3 「Console クラス」の解説に、「この型は、スレッドセーフです。」という記述があります。

 ThreadStartの代わりにParameterizedThreadStartを利用することによって、threadFuncAとthreadFuncBを1つの関数にまとめることができます。ParameterizedThreadStartはObject^型の引数を1つ持つデリゲートで、これを利用して作ったスレッドは、メソッドStart()で開始する際に、Object^型の引数を与えることができます。次のコードでは、ParameterizedThreadStartを使って、スレッドの開始時に調べる数の初期値を与えています。パラメータの型はObject^なので、利用する際にはキャストが必要です。

#include "number.h"
using namespace System;
using namespace System::Threading;

const int N=100;

//startから3つおきに調べる
void threadFunc(Object^ start)
{
  for (int n=(Int32)start; n<=N; n+=3) { //キャストが必要
    if (isPrime(n)) Console::Write("{0} ", n);
  }
}

int main()
{
  Console::Write("2 3 ");

  //開始時に引数を与えられるスレッド
  Thread^ threadA=gcnew Thread(gcnew ParameterizedThreadStart(threadFunc));
  Thread^ threadB=gcnew Thread(gcnew ParameterizedThreadStart(threadFunc));

  threadA->Start(4); //4から調べる
  threadB->Start(5); //5から調べる

  threadA->Join();
  threadB->Join();
}
[サンプル]13-cli-primes2.cpp

 見つかった素数をコンテナに格納するプログラムは少し複雑になります。前節のベクタ同様、.NETのListもスレッドセーフではないので、排他制御が必要です。排他制御には、Mutexオブジェクトを使います。探索の初期値とMutexオブジェクト、結果を格納するListをまとめたクラスStateを作ります。

//計算の状態を保持するクラス
ref struct State
{
  int start; //最初の整数
  Mutex^ mtx; //ロック用のハンドル
  List<int>^ primes; //結果を格納するリストへのハンドル

  State(int start, Mutex^ mtx, List<int>^ primes)
    : start(start), mtx(mtx), primes(primes) {}
};
[サンプル]13-cli-primes3.cpp

 スレッドで実行する関数は、このStateオブジェクトのメンバを利用して計算を行います。

//Stateオブジェクトから状態を読み取って計算する関数
static void threadFunc(Object^ state)
{
  State^ s=dynamic_cast<State^>(state); //キャストが必要
  for (int n=s->start; n<=N; n+=3) {
    if (isPrime(n)) {
      s->mtx->WaitOne(); //ロック
      s->primes->Add(n);
      s->mtx->ReleaseMutex(); //解放
    }
  }
}
[サンプル]13-cli-primes3.cpp

 MutexオブジェクトのメソッドWaitOne()を呼び出すとロックがかかり、ReleaseMutex()を呼び出すと開放されます。コンテナに格納する処理をその間に書くことで、コンテナにアクセスするスレッドを1つに限定することができます。

 全体のコードは次のようになります。

#include "number.h"
using namespace System;
using namespace System::Threading;
using namespace System::Collections::Generic;

const int N=100000;

(クラスStateの定義)

(関数threadFuncの定義)

int main()
{
  DateTime^ start=DateTime::Now; //開始時間
 
  Mutex^ mtx=gcnew Mutex; //ロック用のオブジェクト
  List<int>^ primes=gcnew List<int>; //結果を格納するList
  primes->Add(2); //2は素数
  primes->Add(3); //3は素数

  Thread^ threadA=gcnew Thread(gcnew ParameterizedThreadStart(threadFunc));
  Thread^ threadB=gcnew Thread(gcnew ParameterizedThreadStart(threadFunc));
  //4からmtxを使って計算、結果はprimesに格納
  threadA->Start(gcnew State(4, mtx, primes));
  //5からmtxを使って計算、結果はprimesに格納
  threadB->Start(gcnew State(5, mtx, primes));

  threadA->Join();
  threadB->Join();

  //結果の表示
  Console::WriteLine("There are {0} prime numbers.", primes->Count);
  //並べ替えて最初と最後の10個を表示
  primes->Sort();
  for (int i=0; i<10; ++i) Console::Write("{0} ", primes[i]);
  Console::WriteLine();
  for (int i=primes->Count-10; i<primes->Count; ++i) Console::Write("{0} ", primes[i]);
  Console::WriteLine();

  DateTime^ finish=DateTime::Now; //終了時間
  TimeSpan duration=finish->Subtract(*start); //経過時間の計算
  Console::WriteLine("Elapsed time: {0} sec.", duration.TotalSeconds);
}
[サンプル]13-cli-primes3.cpp

 実行結果は次のとおりです。シングルスレッドの場合(約9.4秒)と同様の結果が、約4.9秒で得られました。

There are 9592 prime numbers.
2 3 5 7 11 13 17 19 23 29
99877 99881 99901 99907 99923 99929 99961 99971 99989 99991
Elapsed time: 4.875 sec.


 INDEX
  [書籍転載]文法からはじめるプログラミング言語Microsoft Visual C++入門
  Visual C++でマルチスレッド・プログラミング
    1.標準C++におけるマルチスレッド
    2.デッドロック
  3..NETにおけるマルチスレッド
    4.OpenMP

インデックス・ページヘ 「文法からはじめるプログラミング言語Microsoft Visual C++入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間