Rubyの非同期処理:FiberとThreadを使いこなす

Rubyの非同期処理:FiberとThreadを使いこなす

Rubyでは、非同期処理を実現するためにFiberとThreadが提供されています。これらを理解し活用することで、効率的でスケーラブルなプログラムを構築できます。本記事では、それぞれの特徴と実践的な使用方法を掘り下げます。

非同期処理とは

非同期処理は、複数の処理を同時に進行させるプログラミング手法です。IO操作や重い計算処理を効率化するために使用されます。

Fiberの概要

Fiberは軽量なコルーチンで、任意のタイミングで制御を切り替えることができます。

fiber = Fiber.new do
  puts "Fiber 開始"
  Fiber.yield
  puts "Fiber 再開"
end

puts "Fiber 呼び出し"
fiber.resume
puts "メイン処理"
fiber.resume

Threadの概要

ThreadはRubyにおける並行処理を実現するクラスです。スレッドは独立した実行単位として動作します。

thread = Thread.new do
  puts "スレッドの処理開始"
  sleep(1)
  puts "スレッドの処理終了"
end

puts "メインスレッド"
thread.join

FiberとThreadの違い

Fiberは明示的に制御を切り替えるコルーチンで、Threadは並行処理をサポートする実行単位です。それぞれの適用シーンを比較します。

Fiberを活用した非同期処理

Fiberを使って非同期の操作をシミュレートします。

fib1 = Fiber.new do
  puts "Fiber 1 - 処理1"
  Fiber.yield
  puts "Fiber 1 - 処理2"
end

fib2 = Fiber.new do
  puts "Fiber 2 - 処理1"
  Fiber.yield
  puts "Fiber 2 - 処理2"
end

fib1.resume
fib2.resume
fib1.resume
fib2.resume

Threadを活用した並行処理

複数のスレッドで並行処理を実現します。

threads = []

5.times do |i|
  threads << Thread.new do
    puts "スレッド #{i} 開始"
    sleep(1)
    puts "スレッド #{i} 終了"
  end
end

threads.each(&:join)

Threadの安全性と同期

スレッド間でのデータ競合を防ぐため、Mutexを使用します。

mutex = Mutex.new
counter = 0

threads = 10.times.map do
  Thread.new do
    mutex.synchronize do
      temp = counter
      sleep(0.1)
      counter = temp + 1
    end
  end
end

threads.each(&:join)
puts "カウンターの最終値: #{counter}"

Fiberを利用したジェネレータの構築

Fiberを使ってデータの生成を行うジェネレータを構築します。

def generator
  Fiber.new do
    10.times do |i|
      Fiber.yield i
    end
  end
end

gen = generator
while gen.alive?
  puts gen.resume
end

ThreadとFiberの組み合わせ

ThreadとFiberを組み合わせて柔軟な非同期処理を実現します。

threads = []

3.times do
  threads << Thread.new do
    fiber = Fiber.new do
      3.times do |i|
        puts "Fiber: #{i}"
        Fiber.yield
      end
    end

    while fiber.alive?
      fiber.resume
      sleep(0.5)
    end
  end
end

threads.each(&:join)

FiberSchedulerによる非同期IO

Ruby 3.0で導入されたFiberSchedulerを使った非同期IOの例です。

require 'socket'

Fiber.set_scheduler(Fiber::Scheduler.new)

server = TCPServer.new(3000)

Fiber.schedule do
  loop do
    client = server.accept
    Fiber.schedule do
      client.puts "こんにちは!"
      client.close
    end
  end
end

非同期処理のパフォーマンス比較

FiberとThreadのパフォーマンスを比較します。

require 'benchmark'

n = 10_000

Benchmark.bm do |x|
  x.report("Fiber") do
    fibers = n.times.map do
      Fiber.new { Fiber.yield }
    end
    fibers.each(&:resume)
  end

  x.report("Thread") do
    threads = n.times.map do
      Thread.new { sleep(0) }
    end
    threads.each(&:join)
  end
end

非同期処理の課題と限界

非同期処理の適用範囲や制限について考察します。

まとめ

RubyのFiberとThreadを使いこなすことで、非同期処理を効果的に実現できます。それぞれの特性を理解し、適切なシナリオで使用することで、パフォーマンスを最大化できます。