書籍転載
文法からはじめるプログラミング言語Microsoft Visual C++入門
並列処理を行うための基礎知識(Visual C++)
――第13章 並列処理〜マルチスレッドプログラミング(後編)――
WINGSプロジェクト 矢吹 太朗(監修 山田 祥寛)
2010/04/28 |
|
|
結果をベクタに格納するプログラムは次のようになります。ベクタにも排他制御が必要であることに注意してください。STLのすべてのコンテナは、複数のスレッドから同時に書き込まれることを想定していません*2。
2つのスレッドが終了した直後のvector内の素数の順番はばらばらなので、結果を表示する前にvectorの内容を並べ替える必要があります。
*2 前節で紹介したTBB は、マルチスレッド環境でSTL のコンテナの代わりとなるもの(concurrent_vector やconcurrent_hash_map)を提供しています。 |
//共通言語ランタイムサポートを使用しない
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <vector>
#include <algorithm>
#include <ctime>
#include "number.h"
using namespace std;
using namespace boost;
const int N=100000;
vector<int> primes; //結果を格納するベクタ
//startから3つおきに素数かどうか調べる
void threadFunc(int start)
{
static mutex mtx;
for (int n=start; n<=N; n+=3) {
if (isPrime(n)) {
mutex::scoped_lock lk(mtx); //vectorにも排他制御が必要
primes.push_back(n); //ベクタに素数を追加
}
}
}
int main()
{
clock_t start=clock(); //開始時間
primes.push_back(2); //2は素数
primes.push_back(3); //3は素数
thread threadA(bind(threadFunc, 4)); //4から調べる
thread threadB(bind(threadFunc, 5)); //5から調べる
threadA.join();
threadB.join();
cout<<"There are "<<primes.size()<<" prime numbers.\n";
sort(primes.begin(), primes.end()); //並べ替えが必要
report(primes.begin(), primes.end()); //結果の表示
clock_t finish=clock();//終了時間
cout<<"Elapsed time: "<<double(finish-start)/CLOCKS_PER_SEC<<" sec.\n";
} |
|
[サンプル]13-primes-boost4.cpp |
実行結果は次のとおりです。シングルスレッドの場合(約9.4秒)と同様の結果が、約4.6秒で得られました。
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.625 sec. |
●13.3.2 デッドロック
前項では、ロックを使った排他制御の方法を紹介しました。排他制御は並列処理には不可欠なものですが、安易に利用するとプログラムが動作しなくなってしまいます。数値をカウントダウンするという簡単な例を使って説明しましょう。
次のようなプログラムを考えます。関数count()は、再帰によって数値をカウントダウンします。
#include <iostream>
using namespace std;
void count(int n)
{
if (n==0) cout<<0<<endl;
else {
cout<<n<<' ';
count(n-1);
}
}
int main() { count(5); } |
|
[サンプル]13-deadlock-01.cpp |
プログラムを実行すると、次のように5から0までカウントダウンされます。
実用的ではありませんが、再帰的に関数を呼び出す際に、新しくスレッドを作ってみましょう。オブジェクトcoutには排他制御が必要なので、プログラムは次のようになると思うかもしれません。
#include <boost/thread.hpp>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;
void count(int n)
{
static mutex mtx;
if (n==0) cout<<0<<endl;
else {
mutex::scoped_lock lock(mtx);
cout<<n<<' ';
thread t(bind(count, n-1)); //新しいスレッドで再帰
t.join();
}
}
int main() { count(5); } |
|
[サンプル]13-deadlock-02.cpp |
このプログラムは動作しません。図のようにelseブロック全体をロックした状態で生成/実行されるスレッドが、自身のelseブロックで待機して先に進めなくなるからです。このように、ロックによって複数のスレッドが待機して動かなくなることをデッドロックと言います。並列プログラミングにおいては、デッドロックが発生しないように細心の注意を払わなければなりません。
|
図13-5 デッドロック |
デッドロックは次のようにして回避することができます。
#include <boost/thread.hpp>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;
void count(int n)
{
static mutex mtx;
if (n==0) cout<<0<<endl;
else {
{ //ロックの範囲をこのブロックに限定
mutex::scoped_lock lock(mtx);
cout<<n<<' ';
}
thread t(bind(count, n-1)); //新しいスレッドで再帰
t.join();
}
}
int main() { count(5); } |
|
[サンプル]13-deadlock-03.cpp |
このコードでは、新たに{}を使ってブロックを作り、ロックする範囲をcoutにアクセスするときだけに限定しています。このように、ロックされる範囲を最小限にすることが排他制御においては重要です。
Insider.NET 記事ランキング
本日
月間