LoginSignup
30
28

More than 5 years have passed since last update.

luaのcoroutine

Last updated at Posted at 2013-11-18

最近、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

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

参考文献

30
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
28