SHOEISHA iD

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

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

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

特集記事

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

mutex ひとめぐり


『同期』ってなに?

 マルチスレッドによる並列化の大きな目的の1つは「速くするため」です。原理的には極めて単純、「みんなで手分けして仕事すれば早く片付くんじゃね?」そのとおり。わかりやすぅい♪ ただし、これには「各人が他を気にせず(同時に)仕事ができるならば」という重要な条件があります。

 例えば、カレーを作るとしましょう。「オレは玉ネギ刻むからお前はそいつを炒めろ」なんて仕事の割り振りだとちっとも速くなりません。オレが玉ネギ刻み終えるまでオマエは鍋を片手にじっと待ってるしかありませんからね。

 「オレは玉ネギ刻むから、オマエはジャガイモの皮むき」ならば同時に仕事ができるから速くなりそうですね。ただし包丁が2本あればね。包丁が1本しかないのなら、1人が使っている間もう1人は包丁が空くまで待ってなくてはなりません(待ってる間に他の仕事ができるなら少しは速くなるかも)。

 このように各人がそれぞれ独立して仕事ができるなら、人数に比例して速く(あるいは数多く)こなせるでしょう。しかしながら数人が協力して仕事をするとなれば多くの場合待ち合わせが必要となります。きざみ玉ネギができるまで鍋を片手に待ってるとか、玉ネギの皮むきつつ包丁が空くのを持ってるとか。

 大事なのは「ちゃんと待つ」ことです。玉ネギみじん切りの完了を待たずに鍋に放り込んだり、玉ネギ刻んでる最中の包丁を奪い取ったりするとデキソコナイのカレーになってしまいます。ちゃんと待つ/待たせること、それによって使用中のリソースを奪わない/奪われないこと、このためのからくりが同期であり、並列プログラミングのキモのひとつとなります。前述のjoin()も同期のひとつです。スレッドが終了するのを待っているのですから。

 もちろん同期を必要としないのが理想です。しかし、1つの仕事を多数で行うのに同期が避けられないのなら、

  • できるだけ少ない回数
  • できるだけ短い時間

 に抑えることが肝要です。同期とは何車線もある高速道路のあちこちに一車線の区間を設けるようなもので、文字通りボトルネックとなります。同期を忘れて失敗する典型例を示します。

#include <tbb/tbb.h>
#include <iostream>

using namespace std;

int main() {
  const int N = 100000;
  int pvalue = 0; // 入金総額
  int nvalue = 0; // 払戻総額
  int value = 0;  // 預金残高
  tbb::tick_count cnt = tbb::tick_count::now();
  // 入金スレッド
  tbb::tbb_thread  deposit([&]() { 
    for ( int i = 0; i < N; ++i ) { 
      pvalue += i; value += i; 
    }
  });
  // 払戻スレッド
  tbb::tbb_thread withdraw([&]() { 
    for ( int i = 0; i < N; ++i ) { 
      nvalue -= i; value -= i; 
    }
  });
  deposit.join();
  withdraw.join();
  cout << (tbb::tick_count::now() - cnt).seconds() << "[sec]  ";
  cout << pvalue << nvalue << '=' << value << endl;
}

 1つの当座預金口座(残高0)に対する入金と払出を、それぞれ別のスレッドで行っています。入金・払出の総額は同じになってるコードですから、両スレッドが処理を終えたとき残高は初期値の0になっているはず。ところが実行結果は0になっていませんし、実行のたびに結果の異なる厄介な現象が起こっています。原因は変数valueに対し複数のスレッドが同時に読み書きしていることにあります。value += iは、

  1. valueを読み
  2. iを加えて
  3. 結果をvalueに書き戻す

 の3つのステップで実行されます。このステップの途中で他方のスレッドが割り込むことで結果が狂います。(単一スレッドでは起こりえない)マルチスレッドで起こりうる困った現象の代表格「データレース」(data race:データの競合)です。

次のページ
atomic

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

特集記事連載記事一覧

もっと読む

この記事の著者

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

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」など、さまざまなカンファレンスを企画・運営しています。

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

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

メールバックナンバー