RubyのThread/MultiThraed/Fiber/Ractor
Thread
いわゆる上から順の逐次実行。
code:rb
def single_thread
# シングルスレッドの例
puts "Task 1 start"
sleep(2) # 2秒待つ
puts "Task 1 end"
puts "Task 2 start"
sleep(1) # 1秒待つ
puts "Task 2 end"
end
single_thread
マルチスレッド
OSのスケジューラによるスレッド切り替えで並行処理を行う
RubyのスレッドはOSのスレッドに依存しており、生成や切り替えにコストがかかる。
code:rb
def multi_thread
# マルチスレッドの例
threads = []
threads << Thread.new do
puts "Thread 1 start"
sleep(2) # 2秒待つ
puts "Thread 1 end"
end
threads << Thread.new do
puts "Thread 2 start"
sleep(1) # 1秒待つ
puts "Thread 2 end"
end
threads.each(&:join) # 全てのスレッドが終了するのを待つ
end
multi_thread
Fiber
OSのスケジューラではなくgoroutineみたいな処理系側でスレッドを管理する。IOが並行に処理できる。OSのコンテキストスイッチはより軽量。
code:rb
def thread_with_fiber
# Fiberの例
fiber1 = Fiber.new do
puts "Fiber 1 step 1"
Fiber.yield # 一時停止
puts "Fiber 1 step 2"
end
fiber2 = Fiber.new do
puts "Fiber 2 step 1"
Fiber.yield # 一時停止
puts "Fiber 2 step 2"
end
# 実行と切り替え
fiber1.resume
fiber2.resume
fiber1.resume
fiber2.resume
end
thread_with_fiber
libev_scheduler is a libev-based fiber scheduler for Ruby 3.0 based on code extracted from Polyphony.
へぇ〜〜こんなのあるんだ
これを使うと何が嬉しい
非同期処理を同期的なコードと似たような感じで記述可能になる
code:rb
require 'libev_scheduler'
# Fiber用のスケジューラを設定
Fiber.set_scheduler(Libev::Scheduler.new)
puts "Start"
# 非同期タスクの実行
3.times do |i|
Fiber.schedule do
sleep(2)
puts "Task #{i} finished" end
end
puts "Tasks scheduled"
Ractor
メモリ分離されており安全に並行処理ができて、GVLの影響を受けないのでマルチCPUを活用できる。データのシェアはRactor間のメッセージパッシングで行う。 code:rb
def pub_sub_ractor
producer = Ractor.new do
5.times do |i|
sleep(rand(3))
end
end
consumer = Ractor.new(producer) do |prod|
while (msg = prod.take)
end
rescue Ractor::ClosedError
puts "Producer closed"
end
consumer.take
end
プロセスの生成がOSか処理系かの違いがある。RactorはRuby側で管理してるので起動コストが低い。
Ractorはメッセージパッシングでデータを受渡できるが、OSプロセスだとシリアライズが必要なのでシリアライズの計算コストがデカくなる可能性がある。
Fiberとの使い分け
RactorはIOバウンドな処理ではなくCPUバウンドな処理に向いている。IOに関してはFiberを使う方が良い。
注意
例外処理を書かないとアプリが死んだりする
参考