Help us understand the problem. What is going on with this article?

Ruby Fiberの基礎まとめ

More than 3 years have passed since last update.

Fiberとは

  • Ruby 1.9から登場 class Fiber (Ruby 2.1.0 リファレンス)
  • 「ある処理を途中まで実行して、その後任意のタイミングで前回の続きから処理を行う」ことが可能になる
  • スレッドはプリエンプティブ、Fiberはノンプリエンプティブ
    • プリエンプティブ: 各スレッドの実行はスケジューラによって自動的行われている
    • ノンプリエンプティブ: こちらで明示的に実行を指示

使い方

基本的な使い方

yieldとresume(fiber_1.rb)
#!/usr/bin/ruby
# vim:set fileencoding=utf-8

fiber = Fiber.new {
  puts 'Hello, Fiber'

  Fiber.yield # 何回でもはさめる

  puts 'Hello (again)'
}

fiber.resume # yeildの時点まで実行
fiber.resume # その次
fiber.resume # その次
Result(fiber_1.rb)
% ruby fiber_1.rb
Hello, Fiber
Hello (again)
fiber_1.rb:14:in `resume': dead fiber called (FiberError)
        from fiber_1.rb:14:in `<main>'

引数と戻り値の使い方

引数と戻り値(fiber_2.rb)
#!/usr/bin/ruby
# vim:set fileencoding=utf-8

fiber = Fiber.new {|first|
  puts first

  second = Fiber.yield('goodbye')

  puts second

  'goodbye (again)'
}

puts fiber.resume('hello')
puts fiber.resume('hello(again)')
Result(fiber_2.rb)
% ruby fiber_2.rb
hello
goodbye
hello(again)
goodbye (again)

ジェネレータ

ジェネレータは、プログラムにおいて、数列の各要素の値などを次々と生成(ジェネレート)し他の手続きに渡す、という機能を持っている手続きである。 参照:wikipedia

Fiberを使うと「膨大な計算結果の配列を一度に構築するのではなく、部分的な結果だけを計算して返し、自家また続きから計算」できるサブルーチンを作ることができます。
つまり一度に大きな配列を作成しない → メモリ効率良い!

フィボナッチ数を返却するジェネレータの例

フィボナッチ数を返すジェネレータ(fiber_fibonach.rb)
fib = Fiber.new {
  a, b = 0, 1

  loop do
    a, b = b, a + b 
    Fiber.yield(a)
  end 
}

p 10.times.map{ fib.resume }
puts fib.resume
puts fib.resume
Result(fiber_fibonach.rb)
% ruby fiber_fibonach.rb
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
89
144

スレッドと組み合わせた場合の制限

Fiberの切り替えはスレッドを超えては実行できない。
他のスレッドのファイバをresumeした際や、Fiberの中のスレッドからFiber.yieldした際はFiberErrorが発生する。

Fiber&Thread(1)
fiber = Fiber.new {
  puts 'OH MY GOD'
}

Thread.fork {
  fiber.resume
}.join
Result(1)
% ruby fiber_other_thread.rb
fiber_other_thread.rb:8:in `resume': fiber called across threads (FiberError)
        from fiber_other_thread.rb:8:in `block in <main>'
Fiber&Thread(2)
# Fiber.yield
fiber = Fiber.new {
  Thread.fork {
    Fiber.yield
  }.join
}

fiber.resume
Result(2)
% ruby fiber_other_thread.rb
fiber_other_thread.rb:5:in `yield': can't yield from root fiber (FiberError)
        from fiber_other_thread.rb:5:in `block (2 levels) in <main>'

参考

パーフェクトRuby

lifull
日本最大級の不動産・住宅情報サイト「LIFULL HOME'S」を始め、人々の生活に寄り添う様々な情報サービス事業を展開しています。
https://lifull.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away