Ruby の Fiber
Fiber は結構複雑なのです。
Fiber.yield がなければ、Fiber は resume で呼び出される proc オブジェクトみたいなもの。ただし、1回しか呼び出せない。
f = Fiber.new do |st| st.upcase end p f.resume("tokyo") #"TOKYO" p f.resume("tokyo") #a.rb:6:in `resume': dead fiber called (FiberError)
ブロック内(子)に Fiber.yield があるとそこで一旦処理が止まる。もう一度(親で)resume されると再開される。
f = Fiber.new do |st| puts "Kyoto" Fiber.yield st.upcase end f.resume("tokyo") #"Kyoto" p f.resume #"TOKYO"
Fiber.yield で止まったあと、次の resume に引数があればそれが Fiber.yield の返り値になる。
f = Fiber.new do |st| puts "Kyoto" puts Fiber.yield st.upcase end f.resume("tokyo") #"Kyoto" p f.resume("Osaka") #"Osaka", "TOKYO"
ブロック変数がない場合は、最初の resume に引数があればそれは捨てられる。
f = Fiber.new do puts Fiber.yield end f.resume("tokyo") f.resume("kyoto") #"Kyoto"
Fiber.yield に引数があれば、resume の返り値になる。
f = Fiber.new do |st| puts "Kyoto" puts Fiber.yield("Nagoya") st.upcase end p f.resume("tokyo") #"Kyoto", "Nagoya" p f.resume("Osaka") #"Osaka", "TOKYO"
ブロック内(子)と外(親)で、断続的に停止しながら通信できるのである。子で止まるときに親に通信できるし、親が再開させるときに子に通信できる。
例1
途中で初期化されるフィボナッチ数列。
f = Fiber.new do a, b = 0, 1 loop do a, b = b, a + b x = Fiber.yield(a) a, b = 0, 1 if x end end ar = [] 6.times {ar << f.resume(false)} ar << f.resume(true) 6.times {ar << f.resume(false)} p ar #=>[1, 1, 2, 3, 5, 8, 1, 1, 2, 3, 5, 8, 13]
例2
Fiber の方では一文字を 0.5秒間隔で表示し、改行があれば文字色を変える。
メインの方は勝手なところで背景色を変えている。両者は独立して動いている。
f = Fiber.new do st = "\n" color = 0 loop do st += Fiber.yield.to_s next if st.empty? chr, st = st[0], st[1..-1] if chr == "\n" print "\e[1;#{(31 + color % 7)}m" #文字色を変更 color += 1 end print chr sleep(0.5) end end f.resume #Fiberの開始 f.resume("Ruby\n") 2.times {f.resume} print "\e[44m" #背景色を変更 f.resume("Fiber\n") 5.times {f.resume} print "\e[45m" #背景色を変更 f.resume("Coroutine\n") 100.times{f.resume}
この場合、Thread でも書けないことはないけれど、同期を取るのが却ってむずかしい。
q = Queue.new Thread.new do color = 0 st = "\n" loop do st += q.pop until st.empty? chr, st = st[0], st[1..-1] if chr == "\n" print "\e[1;#{(31 + color % 7)}m" color += 1 end print chr sleep(0.5) end end end q.push("Ruby\n") sleep(1.5) print "\e[44m" q.push("Fiber\n") sleep(3) print "\e[45m" q.push("Coroutine\n") sleep(10)
背景色を変えるタイミングもむずかしいし、終了前に余分な sleep を入れないようにするのも面倒。