書籍転載
文法からはじめるプログラミング言語Microsoft Visual C++入門
並列処理を行うための基礎知識(Visual C++)
――第13章 並列処理〜マルチスレッドプログラミング(後編)――
WINGSプロジェクト 矢吹 太朗(監修 山田 祥寛)
2010/04/28 |
|
|
■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。
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. |
Insider.NET 記事ランキング
本日
月間