Как да коригирате блокиране в join() в Ruby

Работя в многопоточност в 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}

Не използвам никакво заключване на mutex, но получавам блокиране. Това е резултатът от горния кодов фрагмент:

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

Горният изход ми казва, че блокирането е след като масивът user_ids е нулев и се случва с join и stop на Thread.

Какво всъщност се случва и какво е решението на тази грешка?


person sravan_kumar    schedule 19.01.2012    source източник
comment
отговорът ми полезен ли беше? Решихте ли проблема?   -  person Aliaksei Kliuchnikau    schedule 20.01.2012


Отговори (3)


Най-простият код за възпроизвеждане на този проблем е:

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

Thread::stop → нула

Спира изпълнението на текущата нишка, поставяйки я в състояние „заспиване“ и планира изпълнението на друга нишка.

Thread#join → thr
Thread#join(лимит) → 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
person Aliaksei Kliuchnikau    schedule 19.01.2012

В допълнение към отговора на 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
person akostadinov    schedule 27.08.2015
comment
какво ще стане, ако опашката е http постоянна връзка? като поток на живо и резултатът в потока е случаен, t.join(5) все още работи? - person crazy_phage; 04.10.2015
comment
@crazy_phage, не съм имал този случай на употреба, но не виждам защо да не работи. В случай на постоянна HTTP връзка, предполагам, че прилагате таймаут, след който връзката трябва да бъде затворена, нали? Ако искате да чакате вечно, можете да зададете много голяма стойност, например 10 години. - person akostadinov; 04.10.2015
comment
добре, все едно http връзката е тръба и имам друга нишка за четене от тръбата, ако използвам t.join, а не j.join 5, ще се срине, но не разбрах защо това се случва, защото избягах че в sidekiq дневникът не показва нищо. И така, току-що видях отговора ви и предполагам, че това е причината да се случи това. - person crazy_phage; 05.10.2015

Ако разбера правилно намеренията ви, бих помислил за нещо по-просто (и вероятно по-безопасно, 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)
person Victor Moroz    schedule 19.01.2012