How to fix deadlock in join () in Ruby

I work in multithreading in Ruby. Code snippet:

  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}

I do not use mutex locks, but I am at a dead end. This is the output of the above code snippet:

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: deadlock detected

The above output tells me that a deadlock occurs after the user_idsarray user_idsis null and occurs with joinand stop user_ids.

What is actually happening and what is the solution to this error?

+11
source share
3 answers

The simplest code to reproduce this problem is:

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

Thread :: stop → nil

Stops the execution of the current thread, puts it in a "sleep" state, and schedules the execution of another thread.

# join → thr
# () → thr

thr. , thr , , thr.

, Thread.join , Thread.stop sleep. , , .

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
+25

Alex Kliuchnikau, , #join , Queue#pop. #join .

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
+7

If I get your intentions right, I would consider something simpler (and probably safer, users_ids.pop()from the inside of the stream it looks scary to me):

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)
+1
source

All Articles