SHOEISHA iD

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

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

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

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


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

スレッドから結果を引き取る

 起こしたスレッドから処理の結果を引き取るとなれば、結果を提供するスレッドと引き取るスレッドの間に何らかの"受け皿"を用意することになります。最も安直にはグローバル変数を受け皿に利用するのですが、グローバル変数なんて好んで使うべきシロモノではありませんから、受け皿の参照を引数に渡すのが次善の策となりますか。

list-03 処理の結果をセットする参照を用意する
#include <iostream>
#include <thread>
#include <functional> // ref

int global_result;

/*
 * 0+1+...+n を求める
 *  グローバル変数を介した(イケてない)方法
 */
void sigma_global(int n) {
  int sum = 0;
  for ( int i = 0; i <= n; ++i ) {
    sum += i;
  }
  global_result = sum;
}

/*
 * 0+1+...+n を求め result にセットする
 *  参照を介した(すこしはマシな)方法
 */
void sigma_reference(int n, int& result) {
  int sum = 0;
  for ( int i = 0; i <= n; ++i ) {
    sum += i;
  }
  result = sum;
}

int main() {
  using namespace std;

  int local_result;
  thread thr_g(sigma_global, 10);
  // ※ 参照を渡す際には std::ref で囲むべし ↓ (const参照なら std::cref で)
  thread thr_r(sigma_reference, 10, ref(local_result));

  thr_g.join();
  thr_r.join();
  cout << "イケてない結果: "    << global_result << ", "
       << "少しはマシな結果:  " << local_result << endl;

}

 コレ、少なからず不安が残ります。用意した受け皿はまったく無防備で(自分を含め)誰かが故意にせよ過失にせよ書き換えることができてしまいますから。

 この不安を解消すべく、なんとも巧妙なからくり:promise/futureが用意されています。promiseが受け皿、futureが受け皿からの結果の取り出し口として機能します。

 使い方は少しばかりややこしい。まず結果の受け皿となるpromiseを作り、メンバ関数:get_future()で取り出し口となるfutureを手に入れます。しかるのちスレッド起動時にpromiseを引数として渡します。このときpromiseはコピーできないのでstd::move()でスレッド側(に渡される引数)に移動させます。これにより、はじめに用意したpromiseはカラッポとなり、これに対する書き込みは影響を及ぼしません。スレッド側ではpromise::set_value()で結果をセットし、引き取る側はfuture::get()で取り出します。future::get()は自動的/暗黙的にスレッドの完了を待ちます。結果が得られるまで呼び側に返ってくるわけにはいきませんから。

list-04 promise/futureで結果を引き取る
#include <iostream>
#include <thread>
#include <future> // promise/future
#include <utility> // move

/*
 * 0+1+...+n を求め result にセットする
 *  promiseを介した(イケてる)方法
 */
void sigma_promise(int n, std::promise<int> result) {
  int sum = 0;
  for ( int i = 0; i <= n; ++i ) {
    sum += i;
  }
  result.set_value(sum);
}

int main() {
  using namespace std;

  promise<int> prm;
  future<int> result = prm.get_future();
  // ※ promiseはコピーできないため、std::moveで移動する
  thread thr(sigma_promise, 10, move(prm));
  // 後続するget()がスレッドの終了を待ってくれるので親権放棄して構わない。
  thr.detach(); 

  // 結果は future<T>::get() で受け取る
  cout << "イケてる結果: " << result.get() << endl;
}

 受け皿の参照を関数オブジェクトの引数に渡すんじゃなく、関数オブジェクトの戻り値をスレッドから引き取るにはpackaged_task/futureを用います。関数オブジェクトをpackaged_taskでwrapののちget_futureすれば、戻り値の取り出し口:futureが手に入るってスンポーです。

 1+2+……+n を返す関数:int sigma(int n) をスレッド内で起動し結果を出力してみました。

list-05 packaged_task/futureで戻り値を引き取る
#include <iostream>
#include <thread>
#include <future>  // packaged_task/future
#include <utility> // move

/*
 * 0+1+...+n を返す
 */
int sigma(int n) {
  int sum = 0;
  for ( int i = 0; i <= n; ++i ) {
    sum += i;
  }
  return sum;
}

int main() {
  using namespace std;

  // 関数をpackaged_taskでwrapする
  packaged_task<int(int)> pkg(sigma);
  // packaged_taskからfutureを手に入れる
  future<int> result = pkg.get_future();
  // ※ packaged_taskはコピーできないため、std::moveで移動する
  thread thr(move(pkg), 10);
  // 後続するget()がスレッドの終了を待ってくれるので親権放棄して構わない。
  thr.detach(); 

  // 結果は future<T>::get() で受け取る
  cout << "0+1+...+n = " << result.get() << endl;
}

 上記のより簡便な方法を提供するのが関数:std::async()です。std::async()に関数オブジェクトと引数を与えると、舞台裏でこっそりpackaged_taskでwrapし、スレッド起動と同時にfutureを返してくれます。

list-06 async()で着火し、結果を引き取る
#include <iostream>
#include <thread>
#include <future>  // async/future

/*
 * 0+1+...+n を返す
 */
int sigma(int n) {
  int sum = 0;
  for ( int i = 0; i <= n; ++i ) {
    sum += i;
  }
  return sum;
}

int main() {
  using namespace std;

  // asyncでスレッドを起動し、さらにfutureを手に入れる
  future<int> result = async(sigma, 10);

  // 結果は future<T>::get() で受け取る
  cout << "0+1+...+n = " << result.get() << endl;
}

次のページ
スレッド内で投げられた例外をつかまえる

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

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

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

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

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング