Ruby join()中的死锁

我在ruby中进行multithreading处理。 代码片段是

threads_array = Array.new(num_of_threads) 1.upto(num_of_threads) do |i| Thread.abort_on_exception = true threads_array[i-1] = Thread.new { catch(:exit) do print "s #{i}" user_id = nil loop do user_id = user_ids.pop() if user_id == nil print "a #{i}" Thread.stop() end dosomething(user_id) end end } end #puts "after thread" threads_array.each {|thread| thread.join} 

我没有使用任何互斥锁。 但我得到了死锁..以下是上面代码片段的输出..

s 2s 6s 8s 1s 11s 7s 10s 14s 16s 21s 24s 5s 26s 3s 19s 20s 23s 4s 28s 9s 12s 18s 22s 29s 30s 27s 13s 17s 15s 25a 4a 10a 3a 6a 21a 24a 16a 9a 18a 5a 28a 20a 2a 22a 11a 29a 8a 14a 23a 26a 1a 19a 7a 12fatal:检测到死锁

上面的输出告诉我们,死锁是在user_ids数组为null之后,并且在ruby中发生了Thread类的join()和stop()。实际发生了什么以及这个错误的解决方案是什么?

重现此问题的简单代码是:

 t = Thread.new { Thread.stop } t.join # => exception in `join': deadlock detected (fatal) 

Thread :: stop →nil

停止当前线程的执行,将其置于“hibernate”状态,并调度另一个线程的执行。

线程#join →thr
线程#joed(限制)→thr

调用线程将暂停执行并运行thr。 直到thr退出或直到极限秒已经过去才返回。 如果时间限制到期,则返回nil,否则返回thr。

据我所知,你在线程上调用没有参数的Thread.join并等待它退出,但子线程调用Thread.stop并进入sleep状态。 这是一个deadloc情况 – 主线程等待子线程退出,但子线程正在hibernate而没有响应。

如果使用limit参数调用join ,则子线程将在超时后中止,而不会导致程序死锁:

 t = Thread.new { Thread.stop } t.join 1 # => Process finished with exit code 0 

我建议在使用Thread.exit完成工作后退出工作线程,或者摆脱无限循环并正常到达执行结束线程,例如:

 if user_id == nil raise StopIteration end #or if user_id == nil Thread.exit end 

除了Alex Kliuchnikau的回答之外,我还要补充一点,当线程在等待Queue#pop #join时, Queue#pop #join会引发这个错误。 一个简单而有意识的解决方案是在超时时调用#join

这是来自ruby 2.2.2:

 [27] pry(main)> q=Queue.new => # [30] pry(main)> q << "asdggg" => # [31] pry(main)> q << "as" => # [32] pry(main)> t = Thread.new { [32] pry(main)* while s = q.pop [32] pry(main)* puts s [32] pry(main)* end [32] pry(main)* } asdggg as => # [33] pry(main)> q << "asg" asg => # [34] pry(main)> q << "ashg" ashg => # [35] pry(main)> t.join fatal: No live threads left. Deadlock? from (pry):41:in `join' [36] pry(main)> t.join(5) => nil 

如果我的意图是正确的,我会考虑更简单的东西(也许更安全,来自线程内的users_ids.pop()看起来很可怕):

 user_ids = (0..19).to_a number_of_threads = 3 user_ids \ .each_slice(user_ids.length / number_of_threads + 1) \ .map { |slice| Thread.new(slice) { |s| puts s.inspect } }.map(&:join)