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 を導入して排他制御を行うことで、正しい結果を保証できます。
コメント
コメントを投稿