Hibariya

FiberとSemi-Coroutine

やっと使い方を覚えたFiberについての覚書。組込みライブラリ編。

Fiber

Rubyの組込みライブラリとして提供されているFiber。 ノンプリエンプティブな軽量スレッド。

コンテキストの切り替えはFiber#resumeFiber.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

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 # ウワーッ

 これに使おう、というのがとっさに思い浮かばない。