SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

インテルTBBの同期メカニズム

mutex ひとめぐり


reader_writer_mutex

 次のサンプルは「そろばん塾の演習」です。そろばん塾には100人の生徒と2人の先生がいます。教室には黒板がひとつあり、

  • 先生は100個の数を問題の欄に、その総和を答のマスに書く
  • 生徒は黒板の問題を読み100個の数の総和を求めてこたえあわせをする

 もちろん先生は他方の先生が出題している間、あるいは生徒のうち1人でも黒板を読んでいる間は黒板を書き換えることができませんし、生徒はいずれかの先生が黒板に書いている間は読むことができません。黒板をmutexでガードしなければならないようです。

#include <tbb/tbb.h>
#include <iostream>
#include <array>
#include <cstdlib>
#include <algorithm>
#include <numeric>
#include <functional>

using namespace std;

// 黒板
struct black_board {
  typedef tbb::spin_mutex mutex_type;
  mutex_type mtx;
  array<int,100> question;
  int answer;
};

void run() {
  const int T =    2; // 先生の数
  const int S =  100; // 生徒の数
  const int Q = 1000; // 問題数
  tbb::atomic<int> exams;   // 出題数
  tbb::atomic<int> answers; // 解答数
  tbb::atomic<int> wrongs;  // 誤答数
  bool start = false;
  bool stop = false;

  // 先生のタスク
  auto teachers_task = [&](black_board& b) {
    while ( !start ) tbb::this_tbb_thread::yield();
    while ( !stop ) {
      black_board::mutex_type::scoped_lock lock(b.mtx);
      generate(b.question.begin(), b.question.end(), []() { return rand()%100;});
      b.answer = accumulate(b.question.begin(), b.question.end(), 0);
      ++exams;
    }
  };
  // 生徒のタスク
  auto students_task = [&](black_board& b) {
    array<int,100> notebook;
    int expected;
    while ( !start ) tbb::this_tbb_thread::yield();
    while ( !stop ) {
      {
        black_board::mutex_type::scoped_lock lock(b.mtx);
        copy(b.question.begin(), b.question.end(), notebook.begin());
        expected = b.answer;
      }

      // 総和を求めて
      int answer = 0;
      for ( unsigned i = 0; i < notebook.size(); ++i ) {
        answer += notebook[i];
      }
      // こたえあわせ
      ++answers;
      if ( answer != expected ) {
        ++wrongs;
      }
    }
  };
  black_board board;
  fill(board.question.begin(),board.question.end(),0);
  board.answer = 0;
  exams = 0;
  wrongs = 0;

  tbb::tick_count cnt = tbb::tick_count::now();
  // T個の先生スレッド
  array<tbb::tbb_thread,T> teachers;
  for ( int i = 0; i < T; ++ i ) {
    teachers[i] = tbb::tbb_thread(teachers_task, ref(board));
  }
  // S個の生徒スレッド
  array<tbb::tbb_thread,S> students;
  for ( int i = 0; i < S; ++ i ) {
    students[i] = tbb::tbb_thread(students_task, ref(board));
  }
  start = true; // 開始
  while ( exams < Q ) tbb::this_tbb_thread::yield();
  stop = true; // 終了
  for ( int i = 0; i < T; ++ i ) { teachers[i].join(); }
  for ( int i = 0; i < S; ++ i ) { students[i].join(); }
  double duration = (tbb::tick_count::now() - cnt).seconds();
  cout << " answers/sec: " << answers/duration << '\t';
  cout << " wrongs: " << wrongs << endl;
}

int main() {
  for ( int i = 0; i < 10; ++i ) {
    run();
  }
}

 ちゃんと動いていますけど...でもちょっと待って。生徒のふるまいがぎこちなくありませんか? 生徒は黒板が書き換えられている間は読んじゃいけません。けども他の生徒が読んでいる間は読んでもかまいません。なのにひとつのmutexから実行権を取得しているので「誰も読み書きしていないとき読める」ことになり、先生はこれでいいけど生徒には制限がキツすぎます。「誰も書いていないなら読める」であって欲しいのです。

 tbb::spin_rw_mutexは書き手用と読み手用の2通りの実行権をaquireできます。tbb::spin_rw_mutex::scoped_lockのコンストラクタの第2引数がtrueなら書き手用、falseなら読み手用の実行権でブロックします(省略すれば書き手用)。先のコードでtbb::spin_mutexをtbb::spin_rw_mutexに差し替え、先生に書き手用、生徒に読み手用実行権をaquireさせると、

 ずいぶん速くなっています。生徒がほかの生徒が読んでいるかを気にしなくなったからです。

次のページ
公平なmutex

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/6090 2011/09/01 10:48

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー