C言語でコルーチン(co-routine)(2)
C言語でコルーチンを実装してみる、その2。
前回のコルーチンマクロではローカル変数が使えない、外部から状態を制御できないという問題があった。今回はそれらの問題を解決してみる。まずはサンプルコードを見てほしい。
そうそう、マクロでちまちまやるのは嫌だという人にはPCL(Portable Coroutine Library)というポータブルなコルーチンライブラリがあるよ。PCLはcontext save/restoreでコルーチンを実現しているよ。setjmp、longjmpを駆使するみたい。
Portable Coroutine Library
http://www.xmailserver.org/libpcl.html
前回のコルーチンマクロではローカル変数が使えない、外部から状態を制御できないという問題があった。今回はそれらの問題を解決してみる。まずはサンプルコードを見てほしい。
int hoge(co_routine_t coro){
co_begin(coro);
co_local_valiables{
int a;
int b;
};
co_local.a = 0;
co_local.b = 0;
printf("arg 0x%x\n",(int)co_get_addrword(coro));
while(1){
printf("a=%d b=%d\n",co_local.a++,co_local.b++);
co_yield();
}
co_end;
}
main{
co_routine_t coro;
int stack[32];
coro = co_create(hoge,0xaa,stack,sizeof(stack));
co_call(coro);
co_call(coro);
co_call(coro);
co_call(coro);
}実行結果は次のとおり。% a.exe前回のコルーチンマクロから変わった所は、コルーチンの関数定義、co_beginマクロに引数を追加、co_local_variables/co_localマクロの追加、co_create/co_call関数の追加ぐらいだろうか。順番に見ていこう。
arg=0xaa
a=0 b=0
a=1 b=1
a=2 b=2
a=3 b=3
- int hoge(co_routine_t coro)
今回の実装ではコルーチン関数はco_routine_t型の引数とint型の値を返す必要がある。なもんで、呼び出す度に戻り値が変わる関数を作りたい場合には使えない。 - co_begin,co_end
毎度おなじみの"おまじない"。 - co_local_valiables
ローカル変数の宣言をここで行う。これらの変数はコルーチンが呼び出されている間は消えることはない。ローカル変数へのアクセスはco_localマクロを経由して行う。co_localは単純にスタックのアドレスをローカル変数用の構造体にキャストしているだけ。 - co_yield()
このマクロが呼ばれた時点でコルーチンは処理を中断し、関数を抜ける。次に呼ばれた時は、このマクロの次の行から処理が再開される。 - coro = co_create(hoge,0xaa,stack,sizeof(stack))
co_create関数を使ってコルーチンを作成する。第1引数にコルーチン関数のポインタ、第2引数にコルーチン関数に渡す引数、第3引数にスタックのアドレス、第4引数にスタックのサイズを渡す。スタックのサイズは最低、sizeof(関数ポインタ)+sizeof(コルーチン関数に渡す引数)+sizeof(int)分必要。作成に成功したらスタックのアドレスがco_routine_t型にキャストされて返される。失敗した場合は0が返る。 - co_call(coro)
コルーチン呼び出す。コルーチンでco_yieldマクロが呼ばれたら処理が返ってくる。
そうそう、マクロでちまちまやるのは嫌だという人にはPCL(Portable Coroutine Library)というポータブルなコルーチンライブラリがあるよ。PCLはcontext save/restoreでコルーチンを実現しているよ。setjmp、longjmpを駆使するみたい。
Portable Coroutine Library
http://www.xmailserver.org/libpcl.html
coroutine.h
coroutine.c
#ifndef __COROUTINE_H__
#define __COROUTINE_H__
typedef struct coroutine* coroutine_t;
typedef int(*co_entry_t)(coroutine_t);
typedef void* co_addrword_t;
typedef void* co_stack_t;
typedef int co_size_t;
typedef int co_resume_t;
struct coroutine {
co_entry_t entry;
co_addrword_t addrword;
co_resume_t resume;
};
#define CO_INITIALALIZE (0)
#define CO_INVALID (-1)
#define co_begin(coro) \
void *__stack_addr = (void*)((coro)+1); \
switch((coro)->resume){ \
case CO_INITIALALIZE:;
#define co_yield() \
do{ \
return(__LINE__); \
case __LINE__:; \
}while(0)
#define co_exit() \
return(CO_INVALID)
#define co_end \
} \
co_exit()
#define co_local_valiables \
typedef struct co_local_valiables* co_local_valiables_t; \
struct co_local_valiables
#define co_local \
(*((co_local_valiables_t)__stack_addr))
#define co_get_addrword(coro) \
((coro)->addrword)
coroutine_t co_create(co_entry_t entry,co_addrword_t addrword,co_stack_t stack,co_size_t size);
co_resume_t co_call(coroutine_t coro);
int co_is_valid(coroutine_t coro);
#endif
coroutine.c
#include "coroutine.h"
coroutine_t co_create(co_entry_t entry,co_addrword_t addrword,co_stack_t stack,co_size_t size)
{
struct coroutine *coro = (struct coroutine*)stack;
if(coro && (size>=sizeof(*coro))){
coro->entry = entry;
coro->addrword = addrword;
coro->resume = CO_INITIALALIZE;
return (coroutine_t)coro;
}
return 0;
}
co_resume_t co_call(coroutine_t coro)
{
co_resume_t ret = CO_INVALID;
if(co_is_valid(coro)){
ret = coro->entry(coro);
coro->resume = ret;
}
return ret;
}
int co_is_valid(coroutine_t coro)
{
return (coro && coro->entry && (coro->resume!=CO_INVALID));
}