Ruby – Thread I

r2

Ultimamente sono diventate normali le macchine multi-CPU, quindi è diventata possibile l’esecuzione di più istruzioni contemporaneamente. Anche se questo comporta un rivedere le nostre abitudini riguardo alla programmazione. E il linguaggio che usi deve consentirlo. Vediamo con Ruby.
In questo caso si parla di threads, in italiano sarebbero filoni ma mai sentito, caso mai sottoprocessi.
Considerate il caso di dover fare un certo lavoro: se siete in due il tempo necessario si dimezza (circa), se siete in tre ancora meglio e così via, finché riuscite a suddividere il lavoro e a coordinarvi.
Vediamo un esempio con Ruby (t0.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t0.rb

a = Thread.new{
    i = 1;
    while i<=10
        sleep(1)
        print "#{i} "
        i += 1
    end
    puts "\nfinito"
}

puts "Ecco l'esecuzione del thread lento"
a.join

t0

vediamo che i numeri compaiono a intervalli di un secondo, per via di sleep(1).

Niente di che, però esaminiamo quest’altro (t1.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t1.rb

def func1
   i = 0
   while i<= 2
      puts "func1 at: #{Time.now}"
      sleep(2)
      i += 1
   end
end

def func2
   j = 0
   while j<= 2
      puts "func2 at: #{Time.now}"
      sleep(1)
      j += 1
   end
end

puts "Started At #{Time.now}\n\n"
t1 = Thread.new{func1()}
t2 = Thread.new{func2()}
t1.join
t2.join
puts "\nEnd at #{Time.now}"

t1

Ecco che i due sottoprocessi t1 e t2 definiti alle righe 25 e 26 e lanciati alle righe 27 e 28 vengono eseguiti contemporaneamente come si può vedere dagli output interallacciati. Siccome il thread t2 è più veloce finisce prima.

Ogni thread esegue la sua funzione func1 per t1 e func2 per t2 ma questo non è necessario, ecco due thread che eseguono la stessa funzione (t2.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t2.rb

def func name, delay
   i = 0
   while i <= 2
      puts "#{name} #{Time.now}"
      sleep delay
      i += 1
   end
end
puts "Started At #{Time.now}\n\n"
t1=Thread.new{func "Thread 1:", 2}
t2=Thread.new{func "Thread 2:", 3}
t1.join
t2.join
puts "\nEnd at #{Time.now}"

t2

Come si vede i due processi hanno la stessa velocità. A seconda del carico della macchina può partire prima t1 o t2. Inoltre è inutile (anzi dannoso) eseguire contemporaneamente un numero di thread maggiore al numero delle CPU disponibili; per me solo due.

Visibilità delle variabili dei thread

Un thread può accedere (vedere) una variabile definita nel main, esempio (t3.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t3.rb

var = 0
puts "prima del thread var = #{var}"
a = Thread.new{
    var = 5
}
a.join
puts "dopo il thread var = #{var}"

t3

Vediamo cosa capita alla variabile definita nel thread (t4.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t4.rb

var_main = 0
puts "prima del thread var_main = #{var_main}"
a = Thread.new{
    var_main = 5
    var_thr = 42
}
a.join
puts "dopo il thread var_main = #{var_main}"
puts "la variabile definita nel thread var_thr = #{var_thr}"

t4

OPS! no, le variabili definite nel thread non sono visibili fuori dal thread.

Risorse condivise da più thread, mutex

A questo punto l’ottimo Karthikeyan propone uno script che però da me non funziona (p.141).
Forse non è colpa sua: lui fa riferimento a una versione precedente. Ciò confermerebbe la necessità di fare sempre molta attenzione alla versione che si usa, come già visto in passato.

In ogni caso vediamo cosa succede con 2 thread che usano le stesse variabili (t51.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t51.rb

require 'thread'

count1 = count2 = 0.0
difference = 0.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}"

t51

OPS! difference dovrebbe essere zero, e invece no di parecchio. Il fatto è che il thread spy non è sincronizzato con counter e gli capita di calcolare la differenza mentre i valori count1 e count2 vengono aggiornati.

Il rimedio è di usare un mutex (da mutual exclusion) che sincronizza i due thread (t61.rb):

#!/usr/bin/ruby
# encoding: UTF-8

# t61.rb

require 'thread'
mutex = Mutex.new

count1 = count2 = 0.0
difference = 0.0

counter = Thread.new do
   loop do
      mutex.synchronize do
         count1 += 1
         count2 += 1
      end
    end
end

spy = Thread.new do
   loop do
       mutex.synchronize do
          difference += (count1 - count2).abs
       end
   end
end

sleep 1
mutex.lock
puts "count1 :  #{count1}"
puts "count2 :  #{count2}"
puts "difference : #{difference}"

t61

OK? Sì, ovvio, è più lento. Ma corretto 🙂
Sui thread c’è ancora parecchio da dire, roba per un prossimo post 😉

Annunci
Post a comment or leave a trackback: Trackback URL.

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...

%d blogger hanno fatto clic su Mi Piace per questo: