CodeZine(コードジン)

特集ページ一覧

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

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2018/01/10 14:00
目次

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

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

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;
}

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

バックナンバー

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

著者プロフィール

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

    C++に首まで浸かったプログラマ。 Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"り わんくま同盟でたまにセッションスピーカやったり 中国茶淹れてにわか茶人を気取ってたり、 あと Facebook とか。 著書: - STL標準...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5