Ruby异常如何导致互斥锁解锁?

6

最近,我一直在使用Ruby的线程,并发现了一种稍微出乎意料的行为。在关键部分中,调用raise会导致互斥锁释放。我可以期望在synchronize方法中出现这种情况,因为它有一个块,但是当单独调用lockunlock时,这种情况似乎也会发生。

例如,下面的代码将输出:

$ ruby testmutex.rb 
x sync
y sync

...在这里,我期望y被阻塞直到宇宙热死。

m = Mutex.new


x = Thread.new() do
  begin
    m.lock
      puts "x sync"
      sleep 5
      raise "x err"
      sleep 5
    m.unlock 
  rescue 
  end
end


y = Thread.new() do
  sleep 0.5
  m.lock
    puts "y sync"
  m.unlock 
end


x.join
y.join

为什么即使x线程中的m.unlock从未被执行,y线程仍然被允许运行?

这是一个有趣的观察。但是...你的问题是什么?Ruby内部实现中,“这是如何发生”的问题是什么?为什么会发生这种情况?这是故意的还是bug? - Phrogz
我很好奇它是如何实现的,但主要问题是原因,我相信你已经非常优雅地解释了 --- 谢谢。 - Stephen Wattam
1个回答

4
请注意,如果您从x中删除raiseunlock,行为将是相同的。因此,您有了这样一种情况: x线程锁定互斥量,然后线程结束,互斥量被解锁。
m = Mutex.new
Thread.new{ m.lock; p m.locked? }.join
#=> true

p m.locked?
#=> false

因此,我们可以看出这种情况与"raise"无关。因为您在"raise"周围有一个"begin/rescue"块,所以您比原本退出"x"线程早了5秒钟。
可能解释器会跟踪线程锁定的任何互斥锁,并在线程死亡时自动和有意地解锁它们。(然而,我无法通过源代码检查来支持这一点。这只是一种基于行为的猜测。)

有趣。在 rescueend 之间插入 sleep 很好地说明了这一点。线程释放其锁的概念完全符合我的预期,我想我从未想过 Ruby 会如此聪明。 - Stephen Wattam
1
你可以在源代码中看到它:thread.c:338 - Guilherme Bernal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接