matomomomemo

Fiberについて

Fiberとは

まずはぼんやりとイメージをつかみたい。Rubyのリファレンスによると、

ノンプリエンプティブな軽量スレッド(以下ファイバーと呼ぶ)を提供します。 他の言語では coroutine あるいは semicoroutine と呼ばれることもあります。 Thread と違いユーザレベルスレッドとして実装されています。

とのこと。用語を拾っていく。

ノンプリエンプティブ

Copilot先生に聞いた。ざくっと必要な点を抽出するとこんな感じに。

  • OSからみた、マルチタスク実行における、CPUの割り当て方式のひとつ。
  • OSがCPUを管理しない方式。
  • 構造がシンプルでタスク切り替え時の負荷が少ないメリットと、行儀の悪いプログラムがいるとシステム全体が停止する可能性があるデメリットがある

coroutine

C++のリファレンス によると、

処理途中でのサスペンド(中断)/レジューム(再開)をサポートする一般化された関数として、コルーチンが導入される。

とのこと。 結局リファレンスの定義に戻って、「ユーザーレベルで実装されたThread」ということでイメージがつかめてきた。

基本的な使い方

Rubyのリファレンスのショートチュートリアルからサンプルコードを拝借し、少しデバッグコードを加えた。

f = Fiber.new do
  p "a"
  n = 0
  loop do
    Fiber.yield(n)
    p "c"
    n += 1
  end
end

5.times do
 p f.resume
 p "b"
end

#=> "a"
     0
    "b"
    "c"
     1
    "b"
    "c"
     2
    "b"
    "c"
     3
    "b"
    "c"
     4
    "b"

コードの動きを追っていく。Fiber#resume が呼び出されると、Fiberの初期化時に渡されたブロックの処理が走り、Fiber.yeild で処理が中断される。この時、yeildに渡された引数が、resumeの戻り値になる。次にresumeが呼び出されると、Fiberのブロックの処理が再開され、yeildで中断する。......という流れを繰り返す。

実験

気になったことを試してみる。

処理が終了した子ファイバーを呼び出す

f = Fiber.new do
    Fiber.yield('a')
end

p f.resume
p f.resume
p f.resume

#=> "a"
    nil
    raise FiberError!!

2回目のresumeで例外が発生することを想定していた。しかし、2回目のresumeではnilが返り、3回目のresumeで例外が発生した。処理時間が関連しているのかもと、1回目のresumeの後にsleepを入れてみたが結果は変わらず。 と思ったがそもそもの想定が違った。1回目のresumeでyieldまで処理が走り、2回目のresumeで処理が再開されブロックが終了し、3回目のresumeで終了したFiberインスタンスに対する呼び出しになり例外が発生する。なんてことはない正常な挙動だった。

Schedulerという概念

ブロッキングに関連する概念らしく、こっちがFiberの本領な気がする。が、ボリュームが重そう。なのでこの辺を起点に別途調べる。