Featured image

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?