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

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

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

 結果をベクタに格納するプログラムは次のようになります。ベクタにも排他制御が必要であることに注意してください。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までカウントダウンされます。

5 4 3 2 1 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にアクセスするときだけに限定しています。このように、ロックされる範囲を最小限にすることが排他制御においては重要です。


 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 記事ランキング

本日 月間