Mutex
Thread間の処理の同期にはMutexを使います。
- Mutex#synchronizeで
- Mutexのロックの獲得
- ブロックの処理を実行。
- ロックの解除
を行います。Javaのsynchronizedブロックと似た感じで使えます。
require 'thread' m = Mutex.new ts = [] 3.times { |j| ts << Thread.start { m.synchronize { # ブロック内の処理が同期実行される。 5.times { |i| puts "thread-" << j.to_s << " : " << i.to_s sleep rand * 0.1 } } } } ts.each {|t| t.join }
実行結果です。ブロック内の処理が同期実行されています。
thread-0 : 0 thread-0 : 1 thread-0 : 2 thread-0 : 3 thread-0 : 4 thread-1 : 0 thread-1 : 1 thread-1 : 2 thread-1 : 3 thread-1 : 4 thread-2 : 0 thread-2 : 1 thread-2 : 2 thread-2 : 3 thread-2 : 4
ちなみに、同期実行しない場合次のようになります。
require 'thread' m = Mutex.new ts = [] 3.times { |j| ts << Thread.start { # m.synchronize { # 同期しない 5.times { |i| puts "thread-" << j.to_s << " : " << i.to_s sleep rand * 0.1 } # } } } ts.each {|t| t.join }
thread-0 : 0 thread-1 : 0 thread-2 : 0 thread-0 : 1 thread-2 : 1 thread-1 : 1 thread-2 : 2 thread-0 : 2 thread-2 : 3 thread-1 : 2 thread-0 : 3 thread-2 : 4 thread-1 : 3 thread-0 : 4 thread-1 : 4
明示的なロック
Mutex#lock、Mutex#try_lockで明示的にロックを取得することもできます。
- 獲得したロックはMutex#unlockで解放します
- begin ensure で必ずunlockすること!
- 別の誰かによってMutexが既にロックされていた場合
- Mutex#lock .. ロックが解放されるまで処理がブロックされます
- Mutex#try_lock .. ロックは行われず、falseが返されます
以下はlockを使った同期化のサンプルです。
require 'thread' m = Mutex.new ts = [] 3.times { |j| ts << Thread.start { begin m.lock # ロック 5.times { |i| puts "thread-" << j.to_s << " : " << i.to_s sleep rand * 0.1 } ensure m.unlock end } } ts.each {|t| t.join }
実行結果です。synchronizedと同じく同期実行されています。
thread-0 : 0 thread-0 : 1 thread-0 : 2 thread-0 : 3 thread-0 : 4 thread-1 : 0 thread-1 : 1 thread-1 : 2 thread-1 : 3 thread-1 : 4 thread-2 : 0 thread-2 : 1 thread-2 : 2 thread-2 : 3 thread-2 : 4
try_lockを使ってみます。すでに誰かにロックされていた場合、例外がスローされます。
m = Mutex.new ts = [] 3.times { |j| ts << Thread.start { locked = false begin locked = m.try_lock # ロック raise "lock failed." << j.to_s unless locked 5.times { |i| puts "thread-" << j.to_s << " : " << i.to_s sleep rand * 0.1 } ensure m.unlock if locked # ロック解除 end } } ts.each {|t| begin t.join rescue puts $! end }
実行結果です。2つ失敗しています。
thread-0 : 0 thread-0 : 1 thread-0 : 2 thread-0 : 3 thread-0 : 4 lock failed.1 lock failed.2
注意:2重ロックは許可されない。
注意事項として、ロックされたMutexを再度ロックすると、たとえ同じスレッドであってもエラーになります。
require 'thread' m = Mutex.new p m.lock # ロック # ロックしたものを再度ロック → ロックできない。 p m.try_lock p m.lock
実行結果です。
#<Mutex:0x100f7e00 @locked=true, @waiting=[]> false xxx/thread.rb:100:in `stop': stopping only thread (ThreadError) note: use sleep to stop forever from xxx/thread.rb:100:in `lock' from xxx/w_lock.rb:10
Mutexの代わりにMonitorを使うと、同一スレッドから複数回ロックすることができます。
require 'monitor' m = Monitor.new m.enter # ロック # ロックしたものを再度ロック → ロックできる p m.mon_try_enter # true m.enter
ただし、Monitorの場合、ロックしたスレッド自身でしかロックを解放できません。
m = Monitor.new m.enter t = Thread.new { m.exit } t.join
xxx/monitor.rb:259:in `mon_check_owner': current thread not owner (ThreadError) from xxx/w_monitor.rb:18:in `join' from xxx/w_monitor.rb:18