SHOEISHA iD

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

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

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

特集記事

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

mutex ひとめぐり


atomic

 TBBでこのデータレースを解消するには、先のコードのvalue = 0;を

tbb::atomic<int> value;
value = 0;

 に差し替えるだけで常に正しい結果が得られます。

 atomic(アトミック)とは分割不能の意。一連の操作の途中に他が割り込むことを許しません。tbb::atmic<T>は、これに対する以下の操作

  • 代入:=
  • インクリメント:++
  • デクリメント:--
  • 加算:+=
  • 減算:-=

 など(詳細はリファレンスを参照)が行われている間、ほかのスレッドによる操作をブロックして同期をとります。ブロックされている時間は他の同期メカニズムに比べて非常に短く、パフォーマンスの低下を抑えることができます。データレースの防止策はatomicの利用が第1候補となります。

 残念ながらatomicで扱えるのはint, longなどの整数型、ポインタ, boolに限られていますし、複数の変数をまとめてatomicにはできません。

atomic<int> x;
atomic<long> y;
...
++x; y -= 5;

 とかやったとき、++xとy -= 5との間に他のスレッドによる操作が割り込めてしまいます。atomicが使えるシチュエーションはかなり限定的で「使えりゃラッキー」程度かな、と。

mutexとspin_mutex

 一連の処理の途中に他のスレッドが割り込むのを抑止すべく、TBBにはmutexが用意されています。mutexとはmutual executionの略、「mutual」は「相互に分け合う」とか「共通の/共有した」あるいは「お互いさま」といった意味で、実行権を司るカギみたいなものです。mutexには大きくふたつ、acquire(取得)とrelease(解放)の動作があります。

  • acquire:実行権を手に入れる。誰かが取得済みであれば解放されるまで待つ
  • release:実行権を手放す(解放する)

 要するに、mutexをacquireすればその時点からreleaseするまでの間実行権をキープし、他スレッドのacquireはreleaseまで待たされます。

 当座預金口座のサンプルにmutexを使うと、

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

using namespace std;

void run() {
  const int N = 1000000;
  long long pvalue = 0;
  long long nvalue = 0;
  long long value = 0;
  tbb::mutex mtx;
  
  tbb::tick_count cnt = tbb::tick_count::now();
  tbb::tbb_thread add([&]() { 
            for ( int i = 0; i <= N; ++i ) { 
              tbb::mutex::scoped_lock lock(mtx); 
              pvalue += i; value += i; 
            }});
  tbb::tbb_thread sub([&]() { 
            for ( int i = 0; i <= N; ++i ) { 
              tbb::mutex::scoped_lock lock(mtx); 
              nvalue -= i; value -= i; 
            }});
  add.join();
  sub.join();
  cout << (tbb::tick_count::now() - cnt).seconds() << "[sec]\t";
  cout << pvalue << nvalue << '=' << value << endl;
}

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

 tbb::mutex::scoped_lockオブジェクトはそのコンストラクタでaquireし、デストラクタでreleaseしますから、オブジェクトの存在するスコープを他スレッドから守ってくれます。

 mutexのバリエーションにspin_mutexがあります。上記コードのtbb::mutexをtbb::spin_mutexに差し替えると、

 どうです? ずいぶん速くなっていませんか? この理由は、acquireによる待ち方の違いによるものです。mutexのacquireは実行権が得られないと「使えるようになったら起こしてネ」とOSにお願いし、スレッドが休眠状態に入ります。一方、spin_mutexのaquireは実行権が得られるまでその場で足踏み待機、つまりビジーウェイトします。スレッドの休眠/覚醒状態を切り替えるのにはそれなりの時間的コストがかかるため、起きたまま待ってるspin_mutexの方が速いんです。

 とはいえ、仕事もないのに無駄に足踏みして待っているのですから、他のスレッドの実行時間を圧迫します。特にI/Oの絡むような長い時間をspin_mutexでガードするのは禁忌です。aquireからreleaseまでのガード時間が長いとmutexの方が速くなります。どちらを選ぶかは...case-by-caseでしょうか。

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

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

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

メールバックナンバー