Lua

luaのcoroutine

More than 5 years have passed since last update.

最近、vimの勝手スクリプトとかもluaで書いたりしていて、便利だなぁって思っています。

coroutineについてメモしておく。


coroutineの大体

coroutineは独自のluaスタックとローカル変数を持つスレッド的な物。

スレッド的だけれど、OSに制御が有るわけではなく、制御を明け渡すならロジック自体がそれを通知しないといけない。

create()するとコルーチンが作られる。resume()で実行/復帰、コルーチン内でyield()すると制御を戻せる。この時、戻り値として値をやり取りしたりもできる。

なんか適当に機能を使いまくると、こんな感じ。


coroutine1.lua

local colo = coroutine.create(

function( init )
if init == nil then init = 0 end

local i = init
while i < 10 do
coroutine.yield(i)
i = i+1
end
assert( false )

return -1
end
)

repeat
local bStat, vRet = coroutine.resume( colo, 5 )
if bStat then
print("->", vRet )
else
print("assert! -> ", vRet)
end
until coroutine.status( colo ) == "dead"



output

->  5

-> 6
-> 7
-> 8
-> 9
-> -1


coroutine.create()


coroutine.create( fn )


create()すると、新しいコルーチンが返される。fnは関数でなければならない。

この関数fnが、コルーチンが走行中に実行される関数になる。


coroutine.resume()


coroutine.resume( co, ... )


resume()するとコルーチンcoの実行を開始/再開できる。coに続いて付けた値で、コルーチンの関数fnの引数と出来る。(ただしもちろん開始の時だけ)

この関数の第一戻り値では、コルーチンがアサートで死んだかどうかが返される。このとき、親のスレッドは死なないので外側からハンドリングすることができる。


output

assert! ->  coroutine1.lua:4: assertion failed!


以降は、関数 or yield()で戻された値が第二・第三・・・と帰っていく。


coroutine.status()


coroutine.status( coro )


status()するとスレッドの状態が返される。これらの値とその状態はこんな感じ。


意味

suspend
コルーチンは新品、もしくはyield()で停止中。resume()可能。

normal
アクティブだが止まっている。自分の親。

running
現在実行中。自分自身。

dead
return/assertで既に終了している。


coroutine.yield()


coroutine.yield( ... )


コルーチンの実行を中断する。次回resume()されたときは、yieldの次の行から実行される。ここで、引数に値を与えると呼び出し元のresume()の第二戻り値以降へ返すことができる。

当然だが、resume()されるまでスタックやローカル変数は保全されている。


coroutine.running()


coroutine.running()


走行中のコルーチンを返す。つまり自分自身。


running.lua

local colo = coroutine.create(

function( )
print( coroutine.status(coroutine.running()))
return -1
end
)

repeat
coroutine.resume( colo, 0 )
until coroutine.status( colo ) == "dead"



output

running



coroutine.wrap()


coroutine.wrap(fn)


wrapはcreate()のひねくれた版で、関数的に呼び出すとresumeされるコルーチンを返す。ただし、このオブジェクトはthreadオブジェクトではなくてfunctionオブジェクトなので、一切にcoroutine.*系関数が使えない。また、通常のコルーチンではエラーは分離されていたけれど、wrap()で作ったコルーチンのエラーは親へ伝播する。

使い所はなかなかだが、場合によってはすっきり書ける。


wrap.lua

local wcolo = coroutine.wrap(

function( i )
while true do
i = i + coroutine.yield()
print("->", i)
end
end
)

wcolo(1)
wcolo(2)
wcolo(3)
wcolo(4)



output

-> 3

-> 6
-> 10

再帰関数の出力を外でやる、みたいなことをやったりするのにも使える。


参考文献