std::async

Effective Modern C++ の日本語版が去年の9月に発売されていたことを今更知って,買う前に内容を調べていたときにC++11にstd::asyncという機能があることを知りました.調べてみると非常にスグレモノで感動したので使い方などのメモを書いておきます.

使い方は非常に簡単で,std::asyncの引数に実行ポリシー(std::launch::async / std::launch::deferred)と非同期実行する関数オブジェクトを渡してやり,その後asyncの返り値のstd::futureのgetメソッドを呼ぶことで並列処理(あるいは遅延評価)された関数の返り値を得ることができます.

#include <future>
#include <iostream>

// 非同期処理される処理
int func() {
	std::cout << "func " << std::this_thread::get_id() << std::endl;
	return 1;
}

int main(int argc, char** argv) {
	std::cout << "thread " << std::this_thread::get_id() << std::endl;
	std::future<int> result = std::async(std::launch::async, func);      // 非同期実行
//	std::future<int> result = std::async(std::launch::deferred, func);   // 遅延評価
	std::cin.ignore(1);                                                  // キー入力待ち
	std::cout << "result " << result.get() << std::endl;                 // 非同期処理結果を取り出す
	return 0;
}

実行ポリシーにstd::launch::asyncを指定した場合には処理はメインスレッドとは別のスレッドで実行されて,future::getを呼んだ際にそのスレッドの終了を待って結果を返します.そのため,asyncに関数を渡してすぐに処理が開始されます.

// std::launch::asyncを指定した場合
thread 11152     // メインスレッドID
func 4348        // 別スレッドID,メインとは別スレッドで実行されている
<Enter>
result 1

std::launch::deferredを指定するとその処理は遅延評価されて,future::getを呼んだ際にメインスレッド内でその処理が実行されます.処理はfuture::getが呼ばれる瞬間まで行われません.

// std::launch::deferredを指定した場合
thread 11152   // メインスレッドID
<Enter>
func 11152     // メインスレッドと同じスレッドで処理が実行されている
result 1

これだけでもスグレモノなのですが,更にスグレているのがstd::launch::asyncを指定した際にいい感じにスレッドプールを使ってスレッドを使いまわしてくれるところです.
cppreference.com std::asyncには”テンプレート関数asyncは処理を非同期で実行します(スレッドプールを使った別スレッドで実行されます)”(意訳)とありました.正確には “a separate thread which may be part of a thread pool”だったので,スレッドプールを使用しているかどうかはコンパイラの実装依存なんでしょう.
少なくとも確認した限りではVisual C++ 2015ではスレッドプールで実装されているようです.

なので,例えば以下の様なコードを実行すると1つ目と2つ目のasyncは並列に異なるスレッドで実行されますが,それらの処理が終わったあとに呼ばれる3つ目のasyncでは,既に処理の終わっているスレッドを使いまわして処理が実行されます.

int main(int argc, char** argv) {
	auto result1 = std::async(std::launch::async, func);
	auto result2 = std::async(std::launch::async, func);
	std::cin.ignore(1);
	auto result3 = std::async(std::launch::async, func);
	std::cin.ignore(1);
	std::cout << "result1 " << result1.get() << std::endl;
	std::cout << "result2 " << result2.get() << std::endl;
	std::cout << "result3 " << result3.get() << std::endl;
	return 0;
}
// 実行結果
thread 18392	// mainスレッド
func 15276		// 1つ目のasyncスレッド
func 15672		// 2つ目のasyncスレッド
<Enter>
func 15672		// 3つ目のasyncスレッド(2つ目のスレッドの使いまわし)
<Enter>
result1 1
result2 1
result3 1

よく言われるようにスレッドの生成はかなり負荷の高い作業なので,これまで自分でstd::threadを使いまわしていたりしていたのですが,こんなに簡単に(かつエレガントに)std::asyncで非同期処理が実行できると知り感動してしまいました.Effective Modern C++でもstd::threadよりもstd::asyncを使おうというようなことが書いてあるらしいので,今後は基本的にはstd::asyncを使いたいと思います.

一つ不満があるとすると,スレッドプール部分は実装依存なためかプールサイズの設定とかスケジューリング方法とかは設定できないようですが,まあ仕方ない部分ですね.
Effective Modern C++はもう注文したので届くのが楽しみです.

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください