Ruby join()中的死锁

时间:2012-01-19 11:06:51

标签: ruby multithreading deadlock

我正在使用ruby中的多线程。 代码段是

  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()。实际发生了什么,这个错误的解决方案是什么?

3 个答案:

答案 0 :(得分:24)

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

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

Thread :: stop →nil

     

停止执行当前线程,将其置于“睡眠”状态   state,并安排另一个线程的执行。

     

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

     

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

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

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

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

答案 1 :(得分:7)

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

这是来自ruby 2.2.2:

[27] pry(main)> q=Queue.new
=> #<Thread::Queue:0x00000003a39848>
[30] pry(main)> q << "asdggg"
=> #<Thread::Queue:0x00000003a39848>
[31] pry(main)> q << "as"
=> #<Thread::Queue:0x00000003a39848>
[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
=> #<Thread:0x00000003817ce0@(pry):34 sleep>
[33] pry(main)> q << "asg"
asg
=> #<Thread::Queue:0x00000003a39848>
[34] pry(main)> q << "ashg"
ashg
=> #<Thread::Queue:0x00000003a39848>
[35] pry(main)> t.join
fatal: No live threads left. Deadlock?
from (pry):41:in `join'
[36] pry(main)> t.join(5)
=> nil

答案 2 :(得分:1)

如果我的意图是正确的,我会考虑更简单的东西(可能更安全,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)