SHOEISHA iD

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

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

C++11:スレッド・ライブラリひとめぐり

C++11:スレッド・ライブラリひとめぐり【補足編:2】


  • このエントリーをはてなブックマークに追加

 マルチスレッド・アプリケーションでは複数のスレッドが互いに干渉することなくフルスピードでぶん回ってくれるのが最もパフォーマンス高くて理想的。ところが実際にはそうも言ってられません、他のスレッドから邪魔されないよう一時的に他スレッドに待ってもらわなければならないことがしばしば起こります。前回に引き続きC++スレッドサポートライブラリによるスレッドの排他のおはなし。

  • このエントリーをはてなブックマークに追加

mutex:スレッド同士が邪魔しない/させないからくり

 とっても簡単な関数:add()を用意しました。

list01
void add(long* address, long value) {
  *address += value;
}

 どうということはない、addressが指すlong値にvalueを加えるだけの簡単なお仕事です。4つのスレッドがこの関数を呼んで1つのlong値にそれぞれ何度も+1、-1、+2、-2します。

list02
#include <iostream>
#include <thread>
#include <functional>
#include <chrono>
#include <mutex>

// data-race が発生するハダカの足し算
void add(long* address, long value) {
  *address += value;
}

void run(std::function<void(long*, long)> fun, long* address) {
  using namespace std;
  using namespace std::chrono;

  auto task = [=](int n, long v) { while ( n-- ) fun(address, v); };
  const int N = 100000;
  thread threads[4];
  auto start = high_resolution_clock::now();

  // スレッドを4本起こして
  threads[0] = thread(task, N,  1L);
  threads[1] = thread(task, N, -1L);
  threads[2] = thread(task, N,  2L);
  threads[3] = thread(task, N, -2L);

  // 全スレッド終了を待つ
  for (thread& thr : threads) thr.join();
  auto stop = high_resolution_clock::now();

  cout << duration_cast<milliseconds>(stop - start).count() << "[ms]" << flush;

}

int main() {
  using namespace std;

  long count = 0L;

  cout << "data-race:            ";
  count = 0; 
  run(add, &count); 
  cout << ", count = " << count << endl;
}

 4つのスレッドが何度も行う加算と減算は相殺されて最終的には初期値である0に戻るかというとさにあらず、

fig01
fig01

 add()内の処理:*address += valueは、「*addressを読み/valueを加えて/*addressに書く」の3ステップを行います。この3ステップ中に他のスレッドが割り込むことで辻褄が合わなくなるんです。例えば*addressが100のとき、+1するスレッドAと-1するスレッドBがほとんど同時にadd()したとしましょう。両者は*addressを読み、Aは100+1→101、Bは100-1→99を計算します。続いて双方が*addressに書き込むと、どちらが後になるかによって*addressは101か99のいずれかとなり、いずれにせよ期待する100にはなりません。

 add()内で行われる「読んで/計算して/書く」一連の処理はその途中で割り込まれてはならないatomic(不可分)な処理なのです。atomic性が保証されないためにデータの辻褄が合わなくなる現象はdata race(データ競合)と呼ばれています。

 atomicでなくてはならない一連の処理の実行を複数のスレッドに行わせないカラクリがmutex。

 ヘッダ<mutex>に定義されたstd::mutexの主要メンバ関数は、

  • lock():ロックを取得する
  • try_lock():ロックの取得を試みる
  • unlock():ロックを解放する(手放す)

の3つ。ロックは実行権/使用権を手に入れる鍵であり、この鍵を取得できるスレッドはただ1つです。lock()によって使用中となったmutexを他のスレッドがlock()すると、そのスレッドはunlock()によって解放されるまで待ち状態となりlock()から戻ってきません。複数のスレッドがロックを待っている状態でロックが解放されると、いずれか1つのスレッドのみがロックを取得しlock()から抜けてきます。なので、

list03
std::mutex mtx;

void mutex_add(long* address, long value) {
  mtx.lock();
  *address += value;
  mtx.unlock();
}

としておけば複数のスレッドがmutex_add()してもmtx.lock()とmtx.unlock()に挟まれた処理を行えるスレッドは1つだけ(ほかのスレッドは待たされる)となりatomic性が保証されるってスンポーです。

 mutexでガードしていないハダカのadd()と比べてみました。

list04
// std::mutexでガードした足し算
std::mutex mtx;

void mutex_add(long* address, long value) {
  mtx.lock();
  *address += value;
  mtx.unlock();
}

……

int main() {
  using namespace std;

  long count = 0L;

  cout << "data-race:            ";
  count = 0; 
  run(add, &count); 
  cout << ", count = " << count << endl;

  cout << "std::mutex:           ";
  count = 0; 
  run(mutex_add, &count); 
  cout << ", count = " << count << endl;

}
fig02
fig02

 countはめでたく初期値0に戻っていますが、そのかわりちょっと遅くなっています。ロックの取得/解放には、そこそこの時間がかかるんですよ。

 残るメンバ関数:try_lock()は、ロックが取得できるなら取得してtrue、取得できないならあきらめてfalseを返します。スレッドが待ち状態にはなりません。

会員登録無料すると、続きをお読みいただけます

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

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

メールバックナンバー

次のページ
スレッドの3状態

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

  • このエントリーをはてなブックマークに追加
C++11:スレッド・ライブラリひとめぐり連載記事一覧

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/10640 2018/02/18 07:28

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング