Boost.勉強会の資料

アップしておきました。


メモメモ
http://blog.think-async.com/2009/08/composed-operations-coroutines-and-code.html
http://www.okws.org/doku.php?id=okws:tame
http://d.hatena.ne.jp/shinichiro_h/20040106#p1

#boostjp タグあたりから拾ってきた疑問とか。

関数終わりの self.exit() について


例えば int を返す coroutine を作る場合、int を返す関数を渡さす必要があって、例えば 1, 2, 3 と順番に値を返すような場合は、

int f(coroutine<int ()>::self& self)
{
    self.yield(1);
    self.yield(2);
    self.yield(3);
}

こうやって書きたいけど、int を返す関数なのに値を返してないので多分コンパイルエラーになるはずです。
なので

int f(coroutine<int ()>::self& self)
{
    self.yield(1);
    self.yield(2);
    return 3;
}

こんな風に最後の値だけ return するのもありなんだけど、最後だけ違うのが微妙な感じなので、

int f(coroutine<int ()>::self& self)
{
    self.yield(1);
    self.yield(2);
    self.yield(3);
    self.exit();
}

とかやっておくと、exit() の中で必ず例外を投げるような仕組みになっているので、return していなくてもエラーとかにはならないはずです。


コルーチンを途中まで実行している状態で終了させられたらローカル変数とかの破棄はどうするの?
Fiber 使うってことは、注意して使わないと一部のデストラクタが動かないとかあるのでは?


コルーチンの中で例外を投げて正常にデストラクタを呼び出して貰います。
なので途中で終了した場合もちゃんとローカル変数は破棄されます。
簡易的に書くとこんな感じ。

void coroutine::exit()
{
    // 終了フラグ立てる
    exited = true;
    swapcontext(); // コンテキスト切り替え。切り替えた先で安全に終了してくれる。
}
void coroutine::self::yield()
{
    swapcontext(); // ここで切り替わっているときに coroutine::exit() が呼ばれると仮定。
    if (exited)
    {
        throw exit_exception(); // ここで例外投げると無事ローカル変数は破棄される
    }
    // 終了フラグが立ってないなら処理継続
}
void coroutine::self::exit()
{
    throw exit_exception(); // coroutine::exit() と coroutine::self::exit() は別ですよ、と。
}
// f の外では try { f(self); } catch (...) { } みたいなのがある。
void f(coroutine<void ()>::self& self)
{
    std::vector<int> v;
    self.yield(); // yield() 中にコルーチンの破棄が呼び出された場合、ここで例外が発生する。
    // コルーチンが途中で破棄されても v は無事に破棄される
}
今の Coroutine の実装だと実行コストが高くて弾一つ一つにコルーチンを割り当てるのはきついのでは?


実行速度は、ARM とかだと 5 命令程度なので、アセンブラならそんなにコストは無いです。Windows の Fiber とかはそれなりにありそうですが実測したことが無いので分からないです。
メモリに関しては、コルーチン用のスタックを弾一つ一つに割り当てることになるので、ヒープがかなりきついと思います。
コルーチンを作る際にスタックサイズを指定することはできるのですが、弾毎にスタックサイズを指定するのはかなり面倒だったりするので、あまり現実的では無いかもしれないです。
その辺は環境毎に変わってくると思うので柔軟に対応をお願いします!


例外投げられたら組み込みで使いにくい


例外が投げられない環境の場合は Boost.Coroutine は使えないですけど、例えばこんな実装にするのが考えられます。

void coroutine::exit()
{
    // 終了フラグ立てる
    exited = true;
    swapcontext(); // コンテキスト切り替え。切り替えた先で安全に終了してくれる。
}
bool coroutine::self::yield()
{
    swapcontext();
    return exited; // 終了フラグ立ってるかどうかを返す
}
void f(coroutine<void ()>::self& self)
{
    ...
    if (self.yield()) return;
    ...
    if (self.yield()) return;
}

yield() した際に必ずチェックを入れてやります。
こうすれば例外に頼らずに自前で返すことができます。
ただ1ヶ所でもチェックしていないところがあったらバグの原因になるので、あまりやりたくないところです。


pthreadでマルチスレッドな環境で使えるの?
Coruoutine みたいなものを使ってると、スレッドセーフなモジュールが問題を起こすこともありそうじゃない?
スタックのアドレスのランダム化とかの影響はないの?


この辺は分かりませんでした!ごめんなさい!