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; }
だいぶ形になってきました。
これに引数と戻り値が加われば、それなりに使えそうです。