Fiberを使ってみる

Boost.Coroutineがacceptされるまで待てないので、勉強がてら簡易版の作成を画策中です。
WindowsのFiberも使ったことなかったので、まずはそちらを弄ってみました。

#include <boost/noncopyable.hpp>
#include <cstddef>
#include <windows.h>

class fiber : private boost::noncopyable
{
public:
    // スレッド用
    fiber() : handle_(0)
    {
    }

    // 新規ファイバ用
    fiber(std::size_t stack_size, LPFIBER_START_ROUTINE func, void* data)
        : handle_(::CreateFiber(stack_size, func, data))
    {
        // エラー処理略
    }

    ~fiber()
    {
        if (handle_)
            ::DeleteFiber(handle_);
    }

    // ファイバfにコンテキストを切り替える
    void yield_to(fiber& f)
    {
        // ファイバ
        if (handle_)
        {
            // ファイバ同士は簡単に切り替えできる
            ::SwitchToFiber(f.handle_);
        }
        // スレッド
        else
        {
            // ファイバからのyield_to()に備え、スレッドをファイバに変換
            handle_ = ::ConvertThreadToFiber(0);
            // コンテキスト切り替え
            ::SwitchToFiber(f.handle_);
            // ファイバから戻ってきたのでファイバをスレッドに戻す
            ::ConvertFiberToThread();
            // スレッドに戻した目印をつけておく
            handle_ = 0;
        }
    }

private:
    void* handle_;
};

#include <iostream>

fiber main_fiber;

void CALLBACK test_fiber(void* data)
{
    fiber* self = *static_cast<fiber**>(data);
    std::cout << "fiber 1" << std::endl;
    self->yield_to(main_fiber);
    std::cout << "fiber 2" << std::endl;
    self->yield_to(main_fiber);
}

int main()
{
    fiber* self = 0;
    fiber f(8192, &test_fiber, &self);
    self = &f; // OK:ファイバはまだ動いていない

    main_fiber.yield_to(f);
    std::cout << "main 1" << std::endl;
    main_fiber.yield_to(f);
    std::cout << "main 2" << std::endl;
}

Boost.Coroutineではもう少しyield_to()の条件分岐が複雑になっていますが、そちらは解読中です。


Fiberから呼び出し元に戻るのに、自分と呼び出し元2つのfiberにアクセスする必要があります。なので、これをまとめてみます。

class coro_self;
typedef void (*coro_func_ptr)(coro_self&);

// 呼び出し側インタフェース
class coroutine
{
    friend class coro_self;

public:
    coroutine(std::size_t stack_size, coro_func_ptr func)
        : func_(func), callee_(stack_size, startup, this)
    {
    }

    void yield()
    {
        caller_.yield_to(callee_);
    }

private:
    coro_func_ptr func_;
    fiber caller_;
    fiber callee_;

    static void CALLBACK startup(void* data);
};

// コルーチン側インタフェース
class coro_self
{
public:
    coro_self(coroutine* c) : coro_(c) {}

    void yield()
    {
        coro_->callee_.yield_to(coro_->caller_);
    }

private:
    coroutine* coro_;
};

void CALLBACK coroutine::startup(void* data)
{
    coroutine* coro = static_cast<coroutine*>(data);
    coro_self self(coro);
    (*coro->func_)(self);
    coro->callee_.yield_to(coro->caller_);
}

#include <iostream>

void test_fiber(coro_self& self)
{
    std::cout << "fiber 1" << std::endl;
    self.yield();
    std::cout << "fiber 2" << std::endl;
}

int main()
{
    coroutine co(8192, &test_fiber);
    co.yield();
    std::cout << "main 1" << std::endl;
    co.yield();
    std::cout << "main 2" << std::endl;
}

だいぶ形になってきました。
これに引数と戻り値が加われば、それなりに使えそうです。