C++11時代のthreading

始めに

本記事は C++11 Advent Calendar 2011 : ATND の6日目です。

std::thread

C++11時代のthreadの基本は std::thread です。おもむろに #include をしましょう。std::threadはコンストラクタで渡された関数オブジェクトを別スレッドで実行します。

#include <iostream>
#include <thread>

void f() {
    std::cout << "f()" << std::endl;
}

int main() {
    std::thread thr(f);

    thr.join();

    return 0;
}

このプログラムを実行すると f() と表示されるはずです。コンパイルして実行してみます。

 $ g++ -o thr thr.cpp -std=c++0x
 $ ./thr
f()
 $

確かに f() と表示されました。でもこれだけだと、本当にメインではないスレッドで実行されているかわかりませんね。そんな時はstd::this_thread::get_id()を使います。

#include <iostream>
#include <thread>

void f() {
    std::cout << '[' << std::this_thread::get_id() << "] f()" << std::endl;
}

int main() {
    std::cout << '[' << std::this_thread::get_id() << "] main()" << std::endl;
    std::thread thr(f);

    thr.join();

    return 0;
}

実行します。

 $ g++ -o thr thr.cpp -std=c++0x
 $ ./thr
[140006855132992] main()
[140006838933248] f()
 $

mainが実行された時と、fが実行された時のthread idが異なっていて、実行スレッドが異なっていることがわかります。

std::threadのコンストラクタは、template thread(F&& f, Args&& args...)の形になっているものがあります。この形のコンストラクタを呼んでthreadを構築すると、別スレッドでf(std::forward(args)...)が実行されます。

#include <iostream>
#include <thread>

void f(int a, int b) {
    std::cout << a << '/' << b << std::endl;
}

int main() {
    std::thread thr(f, 1, 2);
    thr.join();
    return 0;
}

このプログラムを実行すると

 $ ./thr
1/2
 $

と表示されます。

std::threadに渡される関数オブジェクト(例の中ではf)の返り値型はvoidである必要はありません。intとかstd::stringとかでも大丈夫ですが、std::threadは返り値を捨てるだけであり、呼び出し元のスレッドと受け渡しするシステムはありません。もし、そのような機能が必要な場合はstd::futureなどを使用します。グローバル変数でも解決できますが、あまりスマートではないですね。

C++では普段あまり意識する必要があったりなかったりしますが、C++には例外という切っても切り離せない…とは言い切れないシステムがあります。スレッドを扱う場合、そのスレッドで例外が投げられた時にどういう挙動になるかを把握しておくことは重要です。

ということで例外を投げてみましょう。

#include <iostream>
#include <thread>

void f() {
    throw 1;
}

int main() {
    std::thread thr(f);
    thr.join();
    return 0;
}

実行します。

 $ g++ -o thr thr.cpp -std=c++0x
 $ ./thr
terminate called after throwing an instance of 'int'
Aborted
 $

はい。落ちました。例外が投げられて、それが捕獲されなかった場合、std::terminateが呼ばれるのがC++11の仕様です (N3290: 30.3.1.2.4)。結構困りますね。一々try catchを書くのは面倒ですし、親スレッド側で例外を処理したい場合もあります。C++11では例外をスレッド間でやりとりするための機構として、std::exception_ptrが用意されています。が、一々そんなものを使うのはめんどくさいですね。すなわち、std::threadを直接使うのは非常にめんどくさいので、避けましょうということです。かわりにstd::asyncやstd::packaged_taskを使います。

#include <iostream>
#include <thread>
#include <future>

int f(int x, int y) {
    if (x < 0 || y < 0) throw "f";
    return 100;
}

int main() {
    std::future<int> f1 = std::async(std::launch::async, f, 100, 200);
    std::cout << f1.get() << std::endl;

    std::future<int> f2 = std::async(std::launch::async, f, 100, -200);
    try {
        std::cout << f2.get() << std::endl;
    } catch (...) {
        std::cout << "catch!!!" << std::endl;
    }
    return 0;
}

このプログラムを実行すると以下の様になります。

 $ g++ -o thr thr.cpp -std=c++0x
 $ ./thr
100
catch!!!
 $

std::asyncを使うと、ライブラリが勝手にいろいろ面倒を見てくれます。std::asyncの返り値型はstd::futureです。細かい挙動は省略しますが、getすると計算結果が帰ってきます。fの中で例外が飛んだ時は、std::future::getでgetしたスレッド側に例外が移譲されます (main後半のように)。なお、std::asyncの実行ポリシーはimplementation definedなので、スレッドを使って計算したい場合はstd::launch::asyncを明示的に渡します。

まとめ

std::threadは非常にprimitiveなので、可能ならば別の高レベルな仕組みを使った方がいいです。

C++11 advent calendar 7日目はid:kikairoyaさんです。よろしくお願いします。