FiberとSemi-Coroutine
やっと使い方を覚えたFiberについての覚書。組込みライブラリ編。
Fiber
Rubyの組込みライブラリとして提供されているFiber。 ノンプリエンプティブな軽量スレッド。
- 生成したファイバは勝手には走らない
- コンテキストの切り替えは自分で
- 親から子へ、子から親へ切り替えることができる
- IO待ちでも切り替わらない
コンテキストの切り替えはFiber#resume
とFiber.yield
で行う。
fiber = Fiber.new {
puts 'Hello Alice.'
Fiber.yield
puts 'Hello Bob.'
Fiber.yield
puts 'Hello Carol.'
}
fiber.resume # "Hello Alice." と表示
fiber.resume # "Hello Bob." と表示
fiber.resume # "Hello Carol." と表示
上の例ではトップレベルが親。ブロックの中が子。親と子でキャッチボールしてる感じ。
Fiber#resume
で親から子へコンテキストを切り替え、Fiber.yield
で子から親へ切り替える。
組込みライブラリとしてのFiberは、親から子、子から親へのみ切り替えられる(それ以外のコンテキストに切り替えることはできない)。
Fiberの引数と戻り値
親からみたときの引数と戻り値
引数は常にFiber#resume
に渡す。
戻り値はFiber#resume
の戻り値。
子からみたときの引数と戻り値
最初の引数はブロック引数として渡される。
それ以降の引数はFiber.yield
の戻り値として与えられる。
戻り値はFiber.yield
の引数として渡す。
Fiber.new
で渡したブロックが終了したときは、最後に評価された式の値が戻り値として返る。
greet3 = Fiber.new {|name|
3.times do
name = Fiber.yield("Hi #{name}")
end
'Good night'
}
greet3.resume('Aron') # => "Hi Aron"
greet3.resume('Billy') # => "Hi Billy"
greet3.resume('Colon') # => "Hi Colon"
greet3.resume # => "Good night"
Coroutine
- 途中まで計算して、一旦値を返して、その後また続きから実行できる仕組み
- 親と子(callerとcallee)という関係があるものをsemi-coroutineと呼ぶ
- 組込みライブラリのFiberはsemi-coroutineの実装と言ってよいのかな
semi-coroutineさん何に使うの
ジェネレータや外部イテレータに使うらしい。 無限リストにも使うらしい。 外部イテレータとしてときどき使うかもしれないEnumeratorではFiberを使ってるようだ。
永遠に終わらない数字のカウントアップを行うジェネレータ。
endless = Fiber.new {
loop.with_index do |_, i|
Fiber.yield(i)
end
}
endless.resume # => 0
endless.resume # => 1
endless.resume # => 2
endless.resume # => 3
endless.resume # => 4
endless.resume # => 5
endless.resume # => 6
# endless...
FiberのAPIを隠して外部イテレータっぽく使ってみる。
class EndlessNumber
def initialize
rewind
end
def rewind
@generator = Fiber.new {
loop.with_index do |_, i|
Fiber.yield(i)
end
}
self
end
def next
@generator.resume
end
end
en = EndlessNumber.new
en.next # => 0
en.next # => 1
en.next # => 2
en.rewind
en.next # => 0
while num = en.next
puts num
end # ウワーッ
これに使おう、というのがとっさに思い浮かばない。