2012-10-25 12 views
5

Mam następujący kod (from a Ruby tutorial):Pętle w wielu wątków

require 'thread' 

count1 = count2 = 0 
difference = 0 
counter = Thread.new do 
    loop do 
     count1 += 1 
     count2 += 1 
    end 
end 
spy = Thread.new do 
    loop do 
     difference += (count1 - count2).abs 
    end 
end 
sleep 1 

puts "count1 : #{count1}" 
puts "count2 : #{count2}" 
puts "difference : #{difference}" 
counter.join(2) 
spy.join(2) 
puts "count1 : #{count1}" 
puts "count2 : #{count2}" 
puts "difference : #{difference}" 

To przykład za korzystanie Mutex.synchronize. Na moim komputerze wyniki różnią się znacznie od samouczka. Po wywołaniu join, liczy się to czasami równa:

count1 : 5321211 
count2 : 6812638 
difference : 0 
count1 : 27307724 
count2 : 27307724 
difference : 0 

a czasami nie:

count1 : 4456390 
count2 : 5981589 
difference : 0 
count1 : 25887977 
count2 : 28204117 
difference : 0 

Nie rozumiem, jak to jest możliwe, że różnica jest jeszcze 0 choć liczy pokazują bardzo różne liczby.

Operacja add prawdopodobnie wygląda tak:

val = fetch_current(count1) 
add 1 to val 
store val back into count1 

i coś podobnego do count2. Ruby może przełączać wykonywanie między wątkami, więc może nie zakończyć zapisu do zmiennej, ale gdy procesor wróci do wątku, powinien kontynuować od linii, w której został przerwany, prawda?

I wciąż jest tylko jeden wątek, który zapisuje do zmiennej. Jak to możliwe, że wewnątrz bloku loop do jest wykonywany znacznie więcej razy?

+0

co ma zrobić 'join (2)'? – uday

+0

daje wątkowi limit (w sekundach) do zakończenia. jeśli tego nie nazwiem, ruby ​​będzie automatycznie pobierał wątki po dojściu do końca programu (tak, że nieskończone 'loop do' zawsze się skończy). zobacz http://www.ruby-doc.org/core-1.9.3/Thread.html#method-i-join, aby uzyskać więcej informacji. – Tombart

+1

To interesujące. W rubinach 1.8 "różnica" zawsze wynosi <> 0, a liczba ta nigdy się nie różni o więcej niż 1, ale w przypadku ruby ​​1.9 "różnica" jest zawsze równa == 0, ale liczba1 i liczba2 są daleko od siebie. – Casper

Odpowiedz

3

Wykonanie

puts "count1 : #{count1}" 

zajmuje trochę czasu (choć może to być krótki). Nie dzieje się to w instancji. Dlatego nie jest tajemnicze, że dwie następujące po sobie linie:

puts "count1 : #{count1}" 
puts "count2 : #{count2}" 

pokazują różne liczby. Po prostu wątek counter przeszedł trochę pętli i zwiększył liczbę zliczeń, podczas gdy pierwszy puts został wykonany.

Podobnie, gdy

difference += (count1 - count2).abs 

oblicza się, liczy się mogą w zasadzie przyrostu podczas count1 odwołuje przed count2 odwołuje. Ale nie ma żadnego polecenia wykonanego w tym przedziale czasowym, i przypuszczam, że czas potrzebny na odnoszenie się do count1 jest znacznie krótszy niż czas potrzebny, aby wątek counter przejdzie przez inną pętlę. Zauważ, że operacje wykonane w tym pierwszym są prawidłowym podzbiorem tego, co zostało zrobione w drugim. Jeśli różnica jest wystarczająco znacząca, co oznacza, że ​​wątek counter nie przeszedł cyklu pętli podczas wywoływania argumentu dla metody -, wówczas count1 i count2 pojawi się jako ta sama wartość.

Przepowiednia będzie, że jeśli umieścić kilka kosztownych obliczeń po przedstawieniu count1 ale przed przedstawieniu count2, następnie difference pokaże się:

difference += (count1.tap{some_expensive_calculation} - count2).abs 
# => larger `difference` 
+0

W rzeczywistości umieszczenie czegoś takiego jak "przespać 0,001" w wątku licznika między licznikami również pokazuje różnicę. Ponieważ wątek licznika jest "zapętlony", zastanawiam się, czy wątek szpiegowski ma nawet szansę na uruchomienie w wersji 1.9. Umieszczenie instrukcji snu w momentalnie zwalnia czas procesora, aby uruchomić wątek szpiegowski, a różnica pokazuje się. – Casper

+1

Dzięki, to ma sens. zastąpienie 'puts' przez' print' count1: # {count1}, count2: # {count2} \ n "" może zmniejszyć różnicę między licznikami. Ponieważ 'puts' jest wykonywany jako dwie komendy i dlatego zajmuje trochę więcej czasu. – Tombart

0

Oto odpowiedź. Sądzę, że założyłeś, że wątki przestają działać po powrocie join(2).

Tak nie jest! Wątki nadal działają, nawet jeśli join(2) zwraca wykonanie (tymczasowo) z powrotem do głównego wątku.

W przypadku zmiany kodu do tego będzie można zobaczyć, co się dzieje:

... 
counter.join(2) 
spy.join(2) 

counter.kill 
spy.kill 

puts "count1 : #{count1}" 
puts "count2 : #{count2}" 
puts "difference : #{difference}" 

To wydaje się działać nieco inaczej w Ruby 1.8, gdzie nitki nie wydają się mieć szansę, aby uruchomić natomiast głównym wątek jest wykonywany.

Samouczek jest prawdopodobnie napisany dla ruby ​​1.8, ale model wątków został zmieniony od tego czasu w wersji 1.9.

W rzeczywistości było to "szczęście", które działało w wersji 1.8, ponieważ wątki nie kończą egzekucji, gdy join(2) powraca ani w wersji 1.8 ani 1.9.