2012-01-19 22 views
5

Pracuję w wielowątkowości w ruby. Fragment kodu jestZakleszczenie w Ruby join()

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} 

Nie używam żadnych blokad mutex. Ale pojawia się impasu .. Po wyjściu z powyższym fragmencie kodu ..

s 2s 6s 8S 1s 11s 10s 14s 16s 7s 21S 24S 5s 26s 20s 23S 19S 3s 4s 28S 9s 12s 18s 29s 30s 27s 22S 13S wykryto zakleszczenie

powyższym przykładzie mówi, że zakleszczenie gdy tablica user_ids traci swoją: 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 dzieje się z join() i stop() klasy Thread w Ruby .. Co tak naprawdę się dzieje i jakie jest rozwiązanie tego błędu?

+0

czy moja odpowiedź była pomocna? Czy rozwiązałeś problem? –

Odpowiedz

19

simples kod do odtworzenia tego problemu jest:

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

Wątek :: zatrzymać → nil

Zatrzymuje wykonanie bieżącego wątku, umieszczając ją w „sen” stan i planuje wykonanie innego wątku.

wątku # dołączyć → Thr
# wątku dołączyć (granica) → Thr

wywołującego wątku zawiesi wykonanie i uruchomienie Thr. Nie zwróci , dopóki nie wyjdzie lub minie limit sekund. Jeśli limit czasu wygasa, zero zostanie zwrócone, w przeciwnym razie thr zostanie zwrócony.

O ile mi zrozumieć zadzwonić Thread.join bez parametru na wątku i czekać na to, aby wyjść, ale wątek dziecko nazywa Thread.stop i idzie do sleep statusu. Jest to sytuacja deadloc - wątek główny czeka na wątek potomny, aby wyjść, ale wątek podrzędny śpi i nie odpowiada.

Jeśli zadzwonisz join z limit parametr następnie nić dziecko zostanie przerwana po limitu czasu bez powodowania impasu do swojego programu:

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

polecam opuścić swoje wątków roboczych po zrobili zadanie z Thread.exit lub dostać pozbyć nieskończonej pętli i dotrzeć do końca wątku wykonania normalnie, na przykład:

if user_id == nil 
    raise StopIteration 
end 

#or 
if user_id == nil 
    Thread.exit 
end 
+0

Przyjemne napisanie; dobra robota. – Phrogz

+0

niesamowite, dziękuje – glebm

0

Jeśli dostanę swoje intencje prawo uważam coś prostsze (i prawdopodobnie bezpieczniejsze, users_ids.pop() od wewnątrz wątku wygląda strasznie mnie):

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) 
5

Oprócz odpowiedzi Alex Kliuchnikau jest, dodam że #join może podnieść ten błąd, gdy wątek czeka na Queue#pop. Prostym i świadomym rozwiązaniem jest wywołanie #join z limitem czasu.

To jest z rubinu 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:[email protected](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 
+0

co jeśli kolejka jest stałym połączeniem http? np. na żywo, a wynik w strumieniu jest losowy, a t.join (5) nadal działa? –

+0

@crazy_phage, nie miałem tego przypadku użycia, ale nie rozumiem, dlaczego nie powinno działać. W przypadku trwałego połączenia HTTP, wydaje mi się, że wprowadzasz limit czasu, po którym połączenie powinno zostać zamknięte, prawda? Jeśli chcesz czekać na zawsze, możesz ustawić bardzo dużą wartość, np. 10 lat. – akostadinov

+0

cóż, to tak, jakby połączenie http było fajką i mam inny wątek do odczytu z potoku, jeśli użyję t.join nie j.join 5, to się rozbije, ale nie rozumiem, dlaczego tak się dzieje, bo I uruchomił to w sidekiq, dziennik niczego nie pokazywał. Tak, właśnie widziałem twoją odpowiedź i myślę, że właśnie dlatego tak się dzieje. –