2012-10-25 24 views
5

Ho il codice seguente (from a Ruby tutorial):loop in più thread

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}" 

È un esempio di uso Mutex.synchronize. Sul mio computer, i risultati sono molto diversi dal tutorial. Dopo aver chiamato join, i conti sono a volte uguali:

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

ea volte no:

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

Non capisco come sia possibile che la differenza è ancora 0 anche se i conteggi mostrano molto diversa numeri.

L'operazione add probabilmente simile a questa:

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

e qualcosa di simile per count2. Ruby può cambiare l'esecuzione tra i thread, quindi potrebbe non finire di scrivere su una variabile, ma quando la CPU torna al thread, dovrebbe continuare dalla riga in cui è stata interrotta, giusto?

E c'è ancora solo un thread che sta scrivendo nella variabile. Com'è possibile che, all'interno del blocco loop do, count2 += 1 venga eseguito molte più volte?

+0

cosa dovrebbe fare 'join (2)'? – uday

+0

fornisce al thread un limite (in secondi) per terminare. se non lo chiamo, ruby ​​raccoglierà automaticamente i thread quando raggiungerà la fine del programma (così finirà sempre l'infinito 'loop do'). vedi http://www.ruby-doc.org/core-1.9.3/Thread.html#method-i-join per maggiori informazioni – Tombart

+1

Questo è interessante. Su ruby ​​1.8 la 'difference' è sempre <> 0, ei conteggi non differiscono mai di più di 1, ma su ruby ​​1.9 la' difference' è sempre == 0 ma count1 e count2 sono molto distanti l'uno dall'altro. – Casper

risposta

3

Esecuzione di

puts "count1 : #{count1}" 

richiede un certo tempo (anche se può essere breve). Non è fatto in un'istanza. Pertanto, non è misterioso che le due righe consecutive:

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

mostrano conteggi diversi. Semplicemente, il thread ha funzionato con alcuni cicli di ciclo e ha incrementato i conteggi mentre è stato eseguito il primo puts.

Allo stesso modo, quando

difference += (count1 - count2).abs 

viene calcolato, i conti possono in linea di principio, mentre incrementi count1 si faccia riferimento prima count2 viene fatto riferimento. Ma non vi è alcun comando eseguito in quell'intervallo di tempo, e immagino che il tempo necessario per fare riferimento a count1 sia molto più breve del tempo necessario affinché il thread counter passi attraverso un altro ciclo. Si noti che le operazioni eseguite nella prima sono un sottoinsieme appropriato di ciò che viene fatto in quest'ultima. Se la differenza è abbastanza significativa, il che significa che il thread non ha attraversato un ciclo di loop durante la chiamata argomento per il metodo -, quindi count1 e count2 appariranno come lo stesso valore.

Una previsione sarà che, se si mette un po 'di calcoli costoso dopo riferimento count1 ma prima fa riferimento a count2, quindi difference verranno visualizzati:

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

In effetti mettere qualcosa come "sleep 0.001' nel controtitolo tra i contatori fa anche apparire la differenza. Poiché il controprogramma è "busy looping", mi chiedo se il thread spia abbia anche molte possibilità di essere eseguito in 1.9. Mettendo l'istruzione sleep in modo istantaneo libera il tempo della CPU per eseguire il thread spia, e la differenza si presenta. – Casper

+1

Grazie, ha senso. sostituendo 'puts' da' print "count1: # {count1}, count2: # {count2} \ n" 'potrebbe diminuire la differenza tra i contatori. Perché 'puts' viene eseguito come due comandi e quindi richiede un po 'più di tempo. – Tombart

0

Ecco la risposta. Penso che tu abbia assunto il presupposto che i thread interrompano l'esecuzione dopo i ritorni join(2).

Questo non è il caso! I thread continuano a essere eseguiti anche se join(2) restituisce l'esecuzione (temporaneamente) al thread principale.

Se si modifica il codice per questo si vedrà che cosa succede:

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

counter.kill 
spy.kill 

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

Ciò sembra funzionare un po 'diverso in Ruby 1.8, dove i fili non sembrano avere la possibilità di eseguire, mentre il principale thread è in esecuzione.

Il tutorial è probabilmente scritto per Ruby 1.8, ma il modello di threading è stato modificato da allora in 1.9.

In effetti era pura "fortuna" che funzionava in 1.8, in quanto i thread non terminano l'esecuzione quando join(2) non torna né 1.8 né 1.9.