If you are developing a multithreaded application, you should be aware that access to shared resources must be protected by a lock. In Ruby, this is most commonly handled using Mutex, which creates an exclusive lock so that only one thread - the one that acquired the lock - can access the resource. However, even with a mutex, you can still run into a deadlock - for instance, if you use recursion and attempt to re-acquire an already held lock within the same thread:
@mut = Mutex.new
@mut_count = 0
def mut_repeat(n)
return if n == 0
@mut.synchronize do
@mut_count += 1
mut_repeat(n - 1)
end
end
Thread.new { mut_repeat(10) }.join # Thread::Mutex#synchronize': deadlock; recursive locking (ThreadError)
puts @mut_count
Fixing this error is straightforward: you can add an extra guard check to see if the lock is already held. Or, use a simpler solution: opt for a Monitor object instead of Mutex.
@mut = Mutex.new
@mut_count = 0
def mut_repeat(n)
return if n == 0
p = Proc.new do
@mut_count += 1
mut_repeat(n - 1)
end
@mut.owned? ? p.call : @mut.synchronize { p.call }
end
[Thread.new { mut_repeat(10) }, Thread.new { mut_repeat(20) }].each(&:join)
puts @mut_count
@mon = Monitor.new
@mon_count = 0
def mon_repeat(n)
return if n == 0
@mon.synchronize do
@mon_count += 1
mon_repeat(n - 1)
end
end
[Thread.new { mon_repeat(10) }, Thread.new { mon_repeat(20) }].each(&:join)
puts @mon_count
Which synchronization primitives do you use in your applications? Have you ever run into deadlock bugs in production? How did you track them down and fix them?