|
書籍転載
文法からはじめるプログラミング言語Microsoft Visual C++入門
Visual C++でマルチスレッド・プログラミング
――第13章 並列処理〜マルチスレッドプログラミング(後編)――
WINGSプロジェクト 矢吹 太朗(監修 山田 祥寛)
2010/04/28 |
|
本コーナーは、日経BPソフトプレス発行の書籍『文法からはじめるプログラミング言語Microsoft Visual C++入門』の中から、特にInsider.NET読者に有用だと考えられる章や個所をInsider.NET編集部が選び、同社の許可を得て転載したものです。基本的に元の文章をそのまま転載していますが、レイアウト上の理由などで文章の記述を変更している部分(例:「上の図」など)や、図の位置などを本サイトのデザインに合わせている部分が若干ありますので、ご了承ください。『文法からはじめるプログラミング言語Microsoft Visual C++入門』の詳細は「目次情報ページ」もしくは日経BPソフトプレスのサイトをご覧ください。 |
ご注意:本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどは@ITのそれとは一致しません。あらかじめご了承ください。 |
■13.3 標準C++におけるマルチスレッド
マルチスレッドのためのクラスであるBoostのthreadを利用する方法を紹介します。前節で述べたように、BoostはWindowsのスレッドとUnix系OSのスレッド(pthread)を抽象化しているため、この節で作成するプログラムはUnix環境でもそのまま動作します。
●13.3.1 スレッドの利用
前節で述べた方針に従って、素数を列挙するプログラムを並列化します。
まず、スレッドの処理は関数として実装します。3の倍数+1となる数だけを調べる関数threadFuncA()は次のようになります(この関数には不備があるので後で修正する必要があります)。
//3の倍数+1となる数だけを調べる
void threadFuncA()
{
for (int n=4; n<=N; n+=3) {
if (isPrime(n)) { //素数だったら表示
cout<<n<<' ';
}
}
} |
|
[サンプル]13-primes-boost1.cpp |
同様に、3の倍数+2となる数だけを調べる関数threadFuncB()は次のようになります。
//3の倍数+2となる数だけを調べる
void threadFuncB()
{
for (int n=5; n<=N; n+=3) {
if (isPrime(n)) { //素数だったら表示
cout<<n<<' ';
}
}
} |
|
[サンプル]13-primes-boost1.cpp |
次のような構文で関数を実行するスレッドを生成します。
これらの関数をスレッドで実行させるプログラムの関数main()は次のようになります(2と3は調べずに表示させています)。
int main()
{
cout<<"2 3 ";
thread threadA(threadFuncA);
thread threadB(threadFuncB);
//スレッドの終了を待つ
threadA.join();
threadB.join();
} |
|
[サンプル]13-primes-boost1.cpp |
メソッドjoin()でスレッドの終了を待ってからプログラムを終了させなければなりません。スレッドの終了を待たずにプログラムが終了すると、計算途中のスレッドが強制終了させられるので、正しい結果が得られません。このように、スレッドの動作をそろえることを「同期を取る」と言います。
2からNまでの素数を求めるプログラムの全体は次のようになります。
//共通言語ランタイムサポートを使用しない
#include <boost/thread.hpp>
#include "number.h"
using namespace std;
using namespace boost;
const int N=100;
(関数threadFuncA()の定義)
(関数threadFuncB()の定義)
(関数main()の定義) |
|
[サンプル]13-primes-boost1.cpp |
このコードは共通言語ランタイムサポートを利用しているとコンパイルできないので、プロジェクトのプロパティで、「共通言語ランタイムサポートを利用しない」ように設定します(2.2.1項を参照)。
ビルドして実行すると、次のような結果になります(環境によって結果は変わります)。
2 3 57 1113 1719 2331 2937 4143 4761 5367 5973 7179 8397 89 |
求めたのは100までの素数なので、この結果は明らかに間違っています(間違った結果が再現されない環境もあるかもしれませんが、以下で述べるように、ここで実行したプログラムには不備があります)。間違った原因は、標準出力のためのオブジェクトcoutに、2つのスレッドから同時に書き込んでいるためです。ここで利用しているオブジェクトcoutは、複数のスレッドから同時に書き込まれることを想定して作られてはいません。ですから、同時に複数のスレッドがcoutを利用することができないようにしなければなりません。
あるオブジェクトを利用するスレッドを限定することを排他制御と言います。排他制御の実現にはmutexと呼ばれるオブジェクトを使います。図13-4のように、あるスレッドがmutexを使ってプログラムの一部をロックすると、別のスレッドは、ロックが解放されるまで待機しなければならなくなります。
|
図13-4 mutexによるオブジェクトcoutへの排他制御の実現 |
オブジェクトcoutをロックするコードを以下に示します。
//共通言語ランタイムサポートを使用しない
#include <boost/thread.hpp>
#include "number.h"
using namespace std;
using namespace boost;
const int N=100;
mutex mtx; //ロックのためのオブジェクト
//3の倍数+1となる数だけを調べる
void threadFuncA()
{
for (int n=4; n<=N; n+=3) {
if (isPrime(n)) { //素数なら表示
mutex::scoped_lock lock(mtx); //スコープ内をロック
cout<<n<<' ';
}
}
}
//3の倍数+2となる数だけを調べる
void threadFuncB()
{
for (int n=5; n<=N; n+=3) {
if (isPrime(n)) { //素数なら表示
mutex::scoped_lock lock(mtx); //スコープ内をロック
cout<<n<<' ';
}
}
}
int main()
{
cout<<"2 3 ";
thread threadA(threadFuncA);
thread threadB(threadFuncB);
threadA.join();
threadB.join();
} |
|
[サンプル]13-primes-boost2.cpp |
mutex::scoped_lockによって、そのスコープ(この例ではif文の内部)がロックされます。スコープの終了とともにロックは解除されます。
実行結果は次のようになり、2から100までの素数がすべて列挙されていることがわかります。複数のスレッドがどのような順番で実行されるかは決まっていないので、このように、ばらばらの順番で出力されます。3で割った余りが1(あるいは2)のものだけに限定すれば、正しい順番で出力されています。
2 3 7 13 19 31 37 43 5 11 17 23 29 41 47 53 61 67 73 79 97 59 71 83 89 |
関数threadFuncA()とthreadFuncB()は、次のように1つにまとめることができます。
//共通言語ランタイムサポートを使用しない
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include "number.h"
using namespace std;
using namespace boost;
const int N=100;
//startから3つおきに素数かどうか調べる
void threadFunc(int start)
{
static mutex mtx;
for (int n=start; n<=N; n+=3) {
if (isPrime(n)) { //素数なら表示
mutex::scoped_lock lock(mtx);
cout<<n<<' ';
}
}
}
int main()
{
cout<<"2 3 ";
thread threadA(bind(threadFunc, 4)); //4から調べる
thread threadB(bind(threadFunc, 5)); //5から調べる
threadA.join();
threadB.join();
} |
|
[サンプル]13-primes-boost3.cpp |
このプログラムでは、次のような構文で、関数threadFunc()の引数を限定して新しい関数を作っています*1。10.1.13項のコラムで紹介した関数オブジェクトを使ってもよいでしょう。
*1 厳密に言えば、作られるのは「新しい関数」ではありません。このことは静的変数が共通であることからもわかります。 |
Insider.NET 記事ランキング
本日
月間