スキップしてメイン コンテンツに移動

Thread.newで安全並列

Thread.newで安全並列

Thread.newとjoin

Ruby実践でマルチスレッドを扱う際、最も基本的な構文は Thread.new です。スレッドを生成し、並列処理を行うことで I/O 待ちや計算負荷を分散できます。以下は簡単な例です。

threads = []
5.times do |i|
  threads << Thread.new do
    puts "スレッド #{i} が開始"
    sleep(rand(1..3))
    puts "スレッド #{i} が終了"
  end
end
threads.each(&:join)
puts "全スレッドが完了しました"

上記コードでは Thread.new で 5 つのスレッドを作成し、join でメインスレッドが全て終了するまで待機します。join を呼ばないと、メインスレッドはすぐに終了してしまい、子スレッドが途中で止まる可能性があります。

Mutexと排他制御

複数スレッドが同じリソースにアクセスすると、データ競合が発生します。これを防ぐために Mutex を使い、排他制御を行います。以下は共有変数への安全な書き込み例です。

counter = 0
mutex = Mutex.new
threads = []

10.times do
  threads << Thread.new do
    1000.times do
      mutex.synchronize do
        counter += 1
      end
    end
  end
end

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

mutex.synchronize ブロック内でのみ counter を更新することで、Race Condition を回避できます。排他制御がないと、同時に複数スレッドが counter を読み取り、書き込みを行うため、期待した値にならないことがあります。

Race Conditionと競合回避

Race Condition は、複数スレッドが同時に共有データを操作する際に発生する不確定な状態です。競合回避のためには、以下のポイントを押さえましょう。

  • 共有データへのアクセスは必ず Mutex で保護する。
  • 可能な限りスレッド間でデータをコピーし、共有を減らす。
  • スレッド数をシステムの CPU コア数に合わせる。
  • デバッグ時は Thread.list で現在のスレッドを確認し、予期せぬスレッドが走っていないかチェックする。

実際に競合が発生したケースを再現すると、以下のような出力が得られます。

Thread 1: 0
Thread 2: 0
Thread 1: 1
Thread 2: 1
... 競合が発生していることが分かる ...

このような状況では、Mutex を導入して排他制御を行うことで、正しい結果を保証できます。

この記事はAIによって作成されました。

コメント