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さんです。よろしくお願いします。

ZeroMQとfork(2)ではまった

僕はかなりはまったのですが、ZeroMQのcontextはfork(2)で複製できません。以下のようなコードを書くと死にます。forkする前にcontextを破棄するか、preforkしておく必要があります。

URLを忘れたのですが、MLでもpreforkしておいてよみたいな流れだったので、バグとかではなく、こういうものなのだと思います。

#include <iostream>
#include <stdexcept>
#include <zmq.hpp>
#include <unistd.h>

bool xfork() {
    pid_t const pid = fork();
    if (pid == -1) {
        throw std::runtime_error("fork");
    }
    return pid == 0;
}

int main() {
    zmq::context_t ctx(1);

    if (xfork()) {
         // なんかする
    } else {
        // なんかする
    }
    return 0;
}

Boost.Serializationの出力形式としてMessagePackを使えるようにしてみた

Boost.ArchiveのMessagePackバックエンドに用いた実装です。まだ試作段階ですが、Boost付属のSerialization用テストは一通り合格しているので、とりあえず動くと思います。

ソースコードgithubにあります。なお、ビルドツールはwafを使用しているため、ビルドにはpythonが必要です。もっとも、cppファイル2個しかありませんが。

メリット

Boostには標準でバイナリアーカイブが付属していますが、バイナリアーカイブクロスプラットフォームではありません。したがって、ネットワーク通信など、送り手と受け手の環境を一致させることが困難な状況でバイナリアーカイブは適切な選択肢ではありません。また、テキストやXMLアーカイブを使用すれば可搬性は高まりますが、データサイズが肥大化してしまいます。

MessagePackはプラットフォームに依存しないフォーマットを採用しているため、バイナリアーカイブと比較して、より広範囲にわたるデータ交換を可能にします。MessagePackは標準で一部の型(例えばstd::vector)の変換アルゴリズムを持っていますが、Boost.Serializationの枠組みに対応することにより、より多くの型をシリアライズすることができます。また、MessagePackは様々な言語でバインディングが提供されているため、他の言語とのデータ交換も可能でしょう。(ただし、Boost.Serializationの変換ルールを他の言語で実装しなければならないため、それなりの手間がかかると思いますが)

未実装

以下の機能は未実装です。

  • woarchive, wiarchive
  • wchar_t型の扱い
  • polymorphic archive

oarchive, iarchiveではwchar_t型はマルチバイト文字列に変換してからシリアライズされます。同様にwoarchive, wiarchiveではマルチバイト文字列はwchar_t型に変換してからシリアライズされますが、現時点では文字変換は未対応です。wchar_t型は数値型として、std::wstringはc_str()で得られる文字列をそのままRAW型として書き出しています。

フォーマット

基本的には、データをそのまま書き出します。

struct A {
    bool a; int b;
    template <class Archive>
    void serialize(Archive& ar) {
        ar & a & b;
    }
};
struct B {
    A a; float b;
    template <class Archive>
    void serialize(Archive& ar) {
        ar & a & b;
    }
};

というクラスがあった場合、

    msgpack::packer<Stream> p;
    B b;
    p << b.a.a << b.a.b << b.b;

とほぼ等価です。実際には、ファイル先頭にBoost.Serializationのヘッダが付与されるため、若干肥大化します。また、Name Value Pair (NVP)の名前は無視され、値のみが出力されます。

clang のライブラリの方で遊んでみた

clang のコンパイラではなく、ライブラリの方で遊んでみた。使用した clang(+LLVM) のバージョンは Subversion レポジトリより取得した r127064 。ちょっと古いが気にしない。

clangのライブラリの方のドキュメントが見つけられなかったので、主にclang tutorialを参考にした。しかしながら、clang tutorialが書かれたのは2008年であり、現在の clang ではコンパイルエラーになるコードもある。

今回作成したのは、main関数が引数を取るか取らないかを出力するプログラムである (clang::FunctionDecl::isMain関数を使えば、ある関数がmain関数であるかどうか判定できるので楽できる)。clang::CompilerInstanceの使い方がこれでいいのか、かなり自信がない。以下ソースコード

コンパイルは以下のようにする*1。 ただし、$(LLVMDIR)はLLVMがインストールされているディレクトリを表す。

$ clang -std=c++0x -fno-rtti -Wall -I$(LLVMDIR)/include \
  -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS -o maintype maintype.cpp $(LLVMDIR)/lib/lib*.a

*1:どのライブラリをリンクすればいいのか、調べるのがめんどかったので、全部リンクすればいいよねっていう(ry

BrainfuckをJITしてみた

BFだとJITしやすいので作ってみた。簡単な最適化をしています。

add/sub [rdi], immの直後にcmp [rdi], 0が来るのは無駄でしかないので、そこの最適化ぐらいはしてみたいですね。

http://gist.github.com/518146

ソースコードは400行ほどあるのでgist上に置いています。Core i5@2.66GHzの環境でhttp://esoteric.sange.fi/brainfuck/bf-source/にあるmandelbrot.bを1.4秒程度で実行する能力があります。

x86_64限定です。Mac OS XLinux上で動く事を確認しました。DEPがONな環境だと、どうなるんでしょうね。

CairoとSDLを組合せてつかう

cairoの描画フロントエンドとしてSDLを使ってみた。ちょっと画面に表示させるだけなのにCocoaだのなんだのを使うのが面倒だったので…。

見た感じ、cairo image surfaceSDL surfaceを比較すると、cairoの方がピクセルフォーマットに対する許容度が低かったので、SDL側でフォーマット差を吸収させています。たとえば、cairo image surfaceではRGB888の24bppフォーマットが使えません。

続きを読む

Boost.Rangeでdropを実装してみた

なぜかBoost.Range.Adaptorsにdropがなかったので作ってみた。slicedでdropを書けなくはないんですが、長さがわからないrangeに適用できないのが問題です。実際はtakeを先に作ったんですが、dropの方が短かったので…。

boost::copy(input | taco::drop(5), output.begin());

のように書けます。g++ 4.5必須。VC系列は動くか見てません。

Random Access Rangeである必要はない*1ので、このような処理も書けます。

std::stringstream ss("1 2 3 4 5 6 7 8 9 10");
boost::copy(boost::make_iterator_range(
                std::istream_iterator<int>(ss),
                std::istream_iterator<int>())
            | taco::drop(5),
            output.begin());

*1:ただしRandom Access Rangeの方が実装効率が良いです

続きを読む