要約
ノンプリエンプティブな軽量スレッド(以下ファイバーと呼ぶ)を提供します。他の言語では coroutine あるいは semicoroutine と呼ばれることもあります。 Thread と違いユーザレベルスレッドとして実装されています。
Thread クラスが表すスレッドと違い、明示的に指定しない限りファイバーのコンテキストは切り替わりません。またファイバーは親子関係を持ちます。Fiber#resume を呼んだファイバーが親になり呼ばれたファイバーが子になります。親子関係を壊すような遷移(例えば自分の親の親のファイバーへ切り替えるような処理)はできません。例外 FiberError が発生します。できることは
- Fiber#resume により子へコンテキストを切り替える
- Fiber.yield により親へコンテキストを切り替える
の二通りです。この親子関係は一時的なものであり親ファイバーへコンテキストを切り替えた時点で解消されます。
ファイバーが終了するとその親にコンテキストが切り替わります。
Ruby 3.1 から fiber を require しなくても、コンテキストの切り替えに制限のない Fiber#transfer が使えます。任意のファイバーにコンテキストを切り替えることができます。
例外
ファイバー実行中に例外が発生した場合、親ファイバーに例外が伝播します。
例:f = Fiber.new do
raise StandardError, "hoge"
end
begin
f.resume # ここでも StandardError が発生する。
rescue => e
p e.message #=> "hoge"
end
ショートチュートリアル
ファイバーは処理のあるポイントで他のルーチンにコンテキストを切り替え、またそのポイントから再開するという目的のために使います。 Fiber.new により与えられたブロックとともにファイバーを生成します。生成したファイバーに対して Fiber#resume を呼ぶことによりコンテキストを切り替えます。子ファイバーのブロック中で Fiber.yield を呼ぶと親にコンテキストを切り替えます。 Fiber.yield の引数が、親での Fiber#resume の返り値になります。
例:f = Fiber.new do
n = 0
loop do
Fiber.yield(n)
n += 1
end
end
5.times do
p f.resume
end
#=> 0
1
2
3
4
以下は内部イテレータを外部イテレータに変換する例です。実際 Enumerator は Fiber を用いて実装されています。
例:def enum2gen(enum)
Fiber.new do
enum.each{|i|
Fiber.yield(i)
}
end
end
g = enum2gen(1..100)
p g.resume #=> 1
p g.resume #=> 2
p g.resume #=> 3
注意
Thread クラスが表すスレッド間をまたがるファイバーの切り替えはできません。例外 FiberError が発生します。
例:f = nil
Thread.new do
f = Fiber.new{}
end.join
f.resume
#=> t.rb:5:in 'Fiber#resume': fiber called across threads (FiberError)
# from t.rb:5:in '<main>'
ノンブロッキングファイバーとスケジューラ
Ruby 3.0 から、ファイバーはブロッキングとノンブロッキングのどちらかの実行コンテキストを持ちます。
Fiber.new は既定でノンブロッキングなファイバーを生成します。
blocking: true を指定するとブロッキングなファイバーになります。
ノンブロッキングファイバーの中でブロックしうる操作を行うと、その操作はスケジューラに委譲されます。ブロックしうる操作とは、IO 待ちやスリープなどです。スケジューラは Fiber.set_scheduler でスレッドごとに設定します。
スケジューラを設定していない場合、ノンブロッキングファイバーはブロッキングファイバーと同じ動作になります。つまりノンブロッキングファイバーであること自体は実行の挙動を変えません。
スケジューラは Ruby 本体では提供されていません。フックメソッドを実装したオブジェクトを利用者が用意します。実装すべきメソッドは Ruby 本体の Fiber::Scheduler のドキュメントで説明されています。
現在の実行コンテキストがどちらであるかは Fiber.blocking? で調べられます。また Fiber.schedule を使うと、スケジューラ経由でノンブロッキングファイバーを生成できます。
目次
- 特異メソッド
- インスタンスメソッド
特異メソッド
self[key] -> object | nilRuby 3.2 から[permalink][rdoc][edit]-
現在のファイバーの fiber storage から key に対応する値を返します。対応する値がない場合は nil を返します。
fiber storage については Fiber#storage を参照してください。
- [PARAM]
key: - キーを Symbol で指定します。
- [EXCEPTION]
TypeError: - key を Symbol として扱えない場合に発生します。
key には String も指定できます。この場合 Symbol に変換されて扱われます。
Fiber[:key] = 1 p Fiber[:key] # => 1 p Fiber[:unknown] # => nil[SEE_ALSO] Fiber.[]=, Fiber#storage
- [PARAM]
self[key] = valueRuby 3.2 から[permalink][rdoc][edit]-
現在のファイバーの fiber storage の key に対応する値を value に設定します。 key に対応する値がまだない場合は追加します。
このファイバーから生成したファイバーには、設定した値が引き継がれます。逆に、生成後の変更は互いに影響しません。
- [PARAM]
key: - キーを Symbol で指定します。
- [PARAM]
value: - 格納する値を指定します。
- [EXCEPTION]
TypeError: - key を Symbol として扱えない場合に発生します。
key には String も指定できます。この場合 Symbol に変換されて扱われます。
Fiber[:key] = 1 Fiber.new do p Fiber[:key] # => 1 Fiber[:key] = 2 p Fiber[:key] # => 2 end.resume # 生成したファイバー側での変更は生成元に影響しない p Fiber[:key] # => 1[SEE_ALSO] Fiber.[], Fiber#storage
- [PARAM]
blocking {|fiber| ... } -> objectRuby 3.2 から[permalink][rdoc][edit]-
ブロックを実行している間だけ、現在のファイバーをブロッキングにします。
ブロックには現在のファイバーが渡されます。現在のファイバーがすでにブロッキングである場合は、単にブロックを実行します。
- [RETURN]
- ブロックの評価結果を返します。
f = Fiber.new do p Fiber.blocking? # => false Fiber.blocking do p Fiber.blocking? # => 1 end p Fiber.blocking? # => false end f.resume[SEE_ALSO] Fiber.blocking?, Fiber/ノンブロッキングファイバーとスケジューラ
blocking? -> false | 1Ruby 3.0 から[permalink][rdoc][edit]-
現在の実行コンテキストがブロッキングである場合に 1 を返します。ノンブロッキングである場合は false を返します。
将来のバージョンで、1 以外のブロッキングレベルを表す値が返るようになる可能性があります。
p Fiber.blocking? # => 1 p Fiber.new { Fiber.blocking? }.resume # => false p Fiber.new(blocking: true) { Fiber.blocking? }.resume # => 1[SEE_ALSO] Fiber#blocking?, Fiber/ノンブロッキングファイバーとスケジューラ
current -> FiberRuby 1.9.3 から[permalink][rdoc][edit]-
このメソッドが評価されたコンテキストにおける Fiber のインスタンスを返します。
例:fr = Fiber.new do Fiber.current end fb = fr.resume p fb.equal?(fr) # => true p Fiber.current # => #<Fiber:0x91345e4> p Fiber.current # => #<Fiber:0x91345e4> current_scheduler -> object | nilRuby 3.1 から[permalink][rdoc][edit]-
現在のスレッドに設定されているスケジューラを返します。ただし現在のファイバーがブロッキングである場合は nil を返します。
現在のファイバーがブロッキングかどうかに関わらずスケジューラを取得したい場合は Fiber.scheduler を使用してください。
[SEE_ALSO] Fiber.scheduler, Fiber.set_scheduler
new(blocking: false, storage: true) {|obj| ... } -> FiberRuby 1.9.3 から[permalink][rdoc][edit]-
与えられたブロックとともにファイバーを生成して返します。ブロックは Fiber#resume に与えられた引数をその引数として実行されます。
ブロックが終了した場合は親にコンテキストが切り替わります。その時ブロックの評価値が返されます。
- [PARAM]
blocking: - 偽を指定するとノンブロッキングなファイバーを生成します。真を指定するとブロッキングなファイバーを生成します。詳しくは Fiber/ノンブロッキングファイバーとスケジューラ を参照してください。
- [PARAM]
storage: - 生成するファイバーの fiber storage を指定します。 true を指定すると呼び出し元のファイバーの fiber storage を複製して引き継ぎます。複製なので、生成後の変更は互いに影響しません。 nil を指定すると引き継ぎません。この場合、最初に書き込んだ時点で空の状態から作られます。 Hash を指定するとその内容で初期化します。キーは Symbol で指定します。 fiber storage については Fiber#storage を参照してください。
例: fiber storage の引き継ぎa = nil f = Fiber.new do |obj| a = obj :hoge end b = f.resume(:foo) p a #=> :foo p b #=> :hogeFiber[:key] = 1 p Fiber.new { Fiber[:key] }.resume # => 1 p Fiber.new(storage: nil) { Fiber[:key] }.resume # => nil p Fiber.new(storage: {key: 2}) { Fiber[:key] }.resume # => 2 - [PARAM]
schedule(*args) {|*args| ... } -> FiberRuby 3.0 から[permalink][rdoc][edit]-
現在のスレッドに設定されているスケジューラを使って、ブロックをノンブロッキングなファイバーで実行します。
ファイバーの生成はスケジューラのフックメソッドに委譲されます。そのため、ブロックがただちに実行されるかどうかはスケジューラの実装に依存します。
- [PARAM]
args: - ブロックの引数として渡されます。
- [RETURN]
- 生成されたファイバーを返します。
- [EXCEPTION]
RuntimeError: - スケジューラが設定されていない場合に発生します。
Fiber.schedule { } # ~> RuntimeError: No scheduler is available![SEE_ALSO] Fiber.set_scheduler, Fiber/ノンブロッキングファイバーとスケジューラ
- [PARAM]
scheduler -> object | nilRuby 3.0 から[permalink][rdoc][edit]-
現在のスレッドに設定されているスケジューラを返します。設定されていない場合は nil を返します。
p Fiber.scheduler # => nil[SEE_ALSO] Fiber.set_scheduler
[SEE_ALSO] Fiber.current_scheduler
set_scheduler(scheduler) -> objectRuby 3.0 から[permalink][rdoc][edit]-
現在のスレッドにスケジューラを設定します。
スケジューラを設定すると、ノンブロッキングファイバーの中でブロックしうる操作を行った際に、スケジューラのフックメソッドが呼ばれるようになります。またスレッドの終了時にスケジューラの close メソッドが呼ばれ、終了していないファイバーの後始末ができるようになっています。
- [PARAM]
scheduler: - スケジューラとして振る舞うオブジェクトを指定します。 nil を指定するとスケジューラを解除します。
- [RETURN]
-
schedulerをそのまま返します。 - [EXCEPTION]
ArgumentError: - scheduler が必要なフックメソッドを実装していない場合に発生します。
Fiber.set_scheduler(Object.new) # ~> ArgumentError: Scheduler must implement #block[SEE_ALSO] Fiber.scheduler, Fiber.schedule, Fiber/ノンブロッキングファイバーとスケジューラ
- [PARAM]
yield(*arg = nil) -> objectRuby 1.9.3 から[permalink][rdoc][edit]-
現在のファイバーの親にコンテキストを切り替えます。
コンテキストの切り替えの際に Fiber#resume に与えられた引数を yield メソッドは返します。
- [PARAM]
arg: - 現在のファイバーの親に渡したいオブジェクトを指定します。
- [EXCEPTION]
FiberError: - Fiber でのルートファイバーで呼ばれた場合に発生します。
a = nil f = Fiber.new do a = Fiber.yield() end f.resume() f.resume(:foo) p a #=> :foo - [PARAM]
インスタンスメソッド
alive? -> boolRuby 1.9.3 から[permalink][rdoc][edit]-
ファイバーが「生きている」時、真を返します。
このメソッドが真を返すのは以下の場合です。
- まだ Fiber#resume されていない
- ブロック内の評価が終了していない (Fiber.yield が呼ばれていない)
fr = Fiber.new{ Fiber.yield "a" } p fr.alive? # => true fr.resume # Fiber.yieldで戻ってくる p fr.alive? # => true fr.resume # ブロック内の評価を終えて戻ってくる p fr.alive? # => false backtrace -> [String]Ruby 3.0 から[permalink][rdoc][edit]backtrace(start) -> [String]backtrace(start, length) -> [String]backtrace(range) -> [String]-
selfが表すファイバーの現在の実行スタックを返します。引数を指定すると、返すスタックの範囲を指定できます。引数の意味は Kernel?.caller と同じです。
ファイバーの実行が開始される前と、終了した後は空の配列を返します。
- [PARAM]
start: - 開始フレームの位置を数値で指定します。
- [PARAM]
length: - 取得するフレームの個数を指定します。
- [PARAM]
range: - 取得したいフレームの範囲を Range で指定します。
def level3 = Fiber.yield def level2 = level3 def level1 = level2 f = Fiber.new { level1 } # 開始前は空 p f.backtrace # => [] f.resume p f.backtrace # => ["t.rb:1:in 'Fiber.yield'", "t.rb:1:in 'Object#level3'", "t.rb:2:in 'Object#level2'", # "t.rb:3:in 'Object#level1'", "t.rb:5:in 'block in <main>'"] p f.backtrace(1, 2) # => ["t.rb:1:in 'Object#level3'", "t.rb:2:in 'Object#level2'"] f.resume # 終了後も空 p f.backtrace # => [][SEE_ALSO] Fiber#backtrace_locations, Kernel?.caller
- [PARAM]
backtrace_locations -> [Thread::Backtrace::Location]Ruby 3.0 から[permalink][rdoc][edit]backtrace_locations(start) -> [Thread::Backtrace::Location]backtrace_locations(start, length) -> [Thread::Backtrace::Location]backtrace_locations(range) -> [Thread::Backtrace::Location]-
Fiber#backtrace と同じですが、実行スタックの各行を Thread::Backtrace::Location の配列で返します。
引数の意味は Fiber#backtrace と同じです。
f = Fiber.new { Fiber.yield } f.resume loc = f.backtrace_locations.first p loc.class # => Thread::Backtrace::Location p loc.lineno # => 1[SEE_ALSO] Fiber#backtrace, Kernel?.caller_locations
blocking? -> boolRuby 3.0 から[permalink][rdoc][edit]-
selfがブロッキングなファイバーである場合に true を返します。ノンブロッキングである場合は false を返します。Fiber.new に
blocking: trueを指定して生成したファイバーがブロッキングです。p Fiber.new { }.blocking? # => false p Fiber.new(blocking: true) { }.blocking? # => true[SEE_ALSO] Fiber.blocking?, Fiber/ノンブロッキングファイバーとスケジューラ
kill -> self | falseRuby 3.3 から[permalink][rdoc][edit]-
selfが表すファイバーを終了させます。捕捉できない例外を発生させて終了させるため、ensure 節は実行されます。
まだ開始されていないファイバーに対して呼んだ場合は、ブロックを実行せずに終了状態にします。すでに終了しているファイバーに対して呼んだ場合は何もしません。
self以外のファイバーを終了させられるのは、そのファイバーが Fiber.yield で停止している場合だけです。selfに対して呼んだ場合は、kill を呼んだ場所で例外が発生します。- [RETURN]
-
selfを返します。すでに kill 済みの場合は false を返します。 - [EXCEPTION]
FiberError: - 他のスレッドに属するファイバーに対して呼んだ場合に発生します。
f = Fiber.new do begin Fiber.yield :a Fiber.yield :b ensure puts "ensure は実行される" end end p f.resume # => :a f.kill # "ensure は実行される" が出力される p f.alive? # => false[SEE_ALSO] Thread#kill
raise -> objectRuby 2.7.0 から[permalink][rdoc][edit]raise(message) -> objectraise(exception, message = nil, backtrace = nil) -> object-
selfが表すファイバーが最後に Fiber.yield を呼んだ場所で例外を発生させます。
Fiber.yield が呼ばれていないかファイバーがすでに終了している場合、 FiberError が発生します。
引数を渡さない場合、RuntimeError が発生します。 message 引数を渡した場合、message 引数をメッセージとした RuntimeError が発生します。
その他のケースでは、最初の引数は Exception か Exception のインスタンスを返す exception メソッドを持ったオブジェクトである必要があります。この場合、2つ目の引数に例外のメッセージを渡せます。また3つ目の引数に例外発生時のスタックトレースを指定できます。
- [PARAM]
message: - 例外のメッセージとなる文字列です。
- [PARAM]
exception: - 発生させる例外です。
- [PARAM]
backtrace: - 例外発生時のスタックトレースです。文字列の配列、または Thread::Backtrace::Location の配列で指定します。
ファイバー内のイテレーションを終了させる例f = Fiber.new { Fiber.yield } f.resume f.raise "Error!" # => Error! (RuntimeError)f = Fiber.new do loop do Fiber.yield(:loop) end :exit end p f.resume # => :loop p f.raise StopIteration # => :exit - [PARAM]
resume(*arg = nil) -> objectRuby 1.9.3 から[permalink][rdoc][edit]-
自身が表すファイバーへコンテキストを切り替えます。自身は resume を呼んだファイバーの子となります。
ただし、Fiber#transfer を呼び出した後に resume を呼び出す事はできません。
- [PARAM]
arg: - self が表すファイバーに渡したいオブジェクトを指定します。
- [RETURN]
- コンテキストの切り替えの際に Fiber.yield に与えられた引数を返します。ブロックの終了まで実行した場合はブロックの評価結果を返します。
- [EXCEPTION]
FiberError: - 自身が既に終了している場合、コンテキストの切替が Thread クラスが表すスレッド間をまたがる場合、自身が resume を呼んだファイバーの親かその祖先である場合に発生します。また、Fiber#transfer を呼び出した後に resume を呼び出した場合に発生します。
f = Fiber.new do Fiber.yield(:hoge) :fuga end p f.resume() #=> :hoge p f.resume() #=> :fuga f.resume() # ~> FiberError: attempt to resume a terminated fiber - [PARAM]
storage -> Hash | nilRuby 3.2 から[permalink][rdoc][edit]storage=(hash)-
selfが表すファイバーの fiber storage を取得、設定します。fiber storage はファイバーごとに持てる記憶領域です。 Fiber.new で生成したファイバーには複製が引き継がれます。スレッドローカル変数がすべてのファイバーで共有されるのに対し、 fiber storage はファイバーを起点とする実行単位の中だけで共有されます。リクエスト ID やロガーの設定のように、暗黙のうちに引き回したい状態に向いています。
個々の値の読み書きには Fiber.[] と Fiber.[]= を使います。 storage が返すのは複製なので、返り値を変更しても fiber storage には反映されません。まだ fiber storage を持たない場合は nil を返します。
storage= は実験的な機能です。呼び出すと実験的な機能である旨の警告が出ます。警告は
-W:no-experimentalオプションで抑制できます。- [PARAM]
hash: - 設定する Hash を指定します。キーは Symbol で指定します。 nil を指定すると fiber storage を空にします。
- [RETURN]
- storage は fiber storage の複製を返します。
- [EXCEPTION]
ArgumentError: - Fiber.current 以外のファイバーに対して呼んだ場合に発生します。
例: 設定Fiber[:key] = 1 p Fiber.current.storage # => {key: 1} # 返り値は複製なので、変更しても fiber storage には影響しない Fiber.current.storage[:key] = 2 p Fiber[:key] # => 1Fiber[:key] = 1 Fiber.current.storage = {other: 2} # 実験的な機能である旨の警告が出る p Fiber[:key] # => nil p Fiber[:other] # => 2[SEE_ALSO] Fiber.new
- [PARAM]
transfer(*args) -> objectRuby 1.9.3 から[permalink][rdoc][edit]-
自身が表すファイバーへコンテキストを切り替えます。
自身は Fiber#resume を呼んだファイバーの子となります。 Fiber#resume との違いは、ファイバーが終了したときや Fiber.yield が呼ばれたときは、ファイバーの親へ戻らずにメインファイバーへ戻ります。
- [PARAM]
args: - メインファイバーから呼び出した Fiber#resume メソッドの返り値として渡したいオブジェクトを指定します。
- [RETURN]
- コンテキスト切り替えの際に、Fiber#resume メソッドに与えられた引数を返します。
- [EXCEPTION]
FiberError: - 自身が既に終了している場合、コンテキストの切り替えが Thread クラスが表すスレッド間をまたがる場合、 Fiber#resume を呼んだファイバーがその親か先祖である場合に発生します。
require 'fiber' fr1 = Fiber.new do |v| :fugafuga end fr2 = Fiber.new do |v| fr1.transfer :fuga end fr3 = Fiber.new do |v| fr2.resume :hoge end p fr3.resume # => :fugafuga - [PARAM]