English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Thread multipli Ruby

每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。

线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。

Ruby 中我们可以通过 Thread 类来创建多线程,Ruby的线程是一个轻量级的,可以以高效的方式来实现并行的代码。

创建 Ruby 线程

要启动一个新的线程,只需要调用 Thread.new 即可:

# 线程 #1 代码部分
Thread.new {
  # 线程 #2 执行代码
}
# 线程 #1 执行代码

Esempio online

以下示例展示了如何在Ruby程序中使用多线程:

Esempio online

#!/usr/bin/ruby
 
def func1
   i=0
   while i<=2
      puts "func1 at: #{Time.now}"
      sleep(2)
      i=i+1
   fine
fine
 
def func2
   j=0
   while j<=2
      puts "func2 at: #{Time.now}"
      sleep(1)
      j=j+1
   fine
fine
 
puts "Started At #{Time.now}"
t1=Thread.new{func1()}
t2=Thread.new{func2()}
t1.join
t2.join
puts "End at #{Time.now}"

Il risultato dell'esecuzione del codice sopra è:

Started At Wed May 14 08:21:54 -0700 2014
func1 at: Wed May 14 08:21:54 -0700 2014
func2 at: Wed May 14 08:21:54 -0700 2014
func2 at: Wed May 14 08:21:55 -0700 2014
func1 at: Wed May 14 08:21:56 -0700 2014
func2 at: Wed May 14 08:21:56 -0700 2014
func1 at: Wed May 14 08:21:58 -0700 2014
End at Wed May 14 08:22:00 -0700 2014

Thread lifecycle

1. Thread creation can use Thread.new, and the same syntax can also be used to create threads with Thread.start or Thread.fork.

2. After creating a thread, there is no need to start it; the thread will execute automatically.

3. The Thread class defines some methods to control threads. The thread executes the code block in Thread.new.

4. The last statement in the thread code block is the thread value, which can be called through the thread's methods. If the thread is completed, the thread value is returned; otherwise, no value is returned until the thread is completed.

5. The Thread.current method returns an object representing the current thread. The Thread.main method returns the main thread.

6. Execute the thread through the Thread.Join method, which will suspend the main thread until the current thread is completed.

thread status

There are 5 states of the thread:

thread statusreturn value
executablerun
sleepingSleeping
exitaborting
Normal terminationfalse
Exception termination occurrednil

Thread and exception

Quando una thread si verifica un'eccezione e non viene catturata da rescue, di solito viene terminata senza avviso. Ma se altre thread stanno aspettando la thread perché di Thread#join, queste thread verranno lanciate con la stessa eccezione.

begin
  t = Thread.new do
    Thread.pass # La thread principale aspetta davvero join
    raise "eccezione non gestita"
  fine
  t.join
rescue
  p $! # => "eccezione non gestita"
fine

Utilizzando questi tre metodi, è possibile far interrompere l'esecuzione dell'interprete quando un thread termina a causa di un'eccezione.

  • Specificare all'avvio dello script.-dOpzione, e esegui in modalità debug.

  • Imposta il segno di spunta utilizzando Thread.abort_on_exception.

  • Imposta un segno di spunta per il thread specificato utilizzando Thread#abort_on_exception.

Dopo aver utilizzato uno di questi tre metodi, l'intero interprete viene interrotto.

t = Thread.new { ... }
t.abort_on_exception = true

Controllo di sincronizzazione delle thread

In Ruby, ci sono tre modi per implementare la sincronizzazione, rispettivamente:

1. Implementare la sincronizzazione attraverso la classe Mutex

2. Implementare la sincronizzazione utilizzando la classe Queue per la gestione dei dati

3. Implementare il controllo di sincronizzazione utilizzando ConditionVariable

La sincronizzazione delle thread è implementata attraverso la classe Mutex

La sincronizzazione delle thread è implementata attraverso la classe Mutex, se più thread devono accedere contemporaneamente a una variabile di programma, questa parte può essere bloccata con lock. Il codice è il seguente:

Esempio online

#!/usr/bin/ruby
 
require "thread"
puts "Sincronizzare i Thread"
 
@num=200
@mutex=Mutex.new
 
def buyTicket(num)
     @mutex.lock
          if @num>=num
               @num=@num-num
               puts "hai acquistato con successo #{num} biglietti"
          else
               puts "mi dispiace, non ci sono abbastanza biglietti"
          fine
     @mutex.unlock
fine
 
ticket1=Thread.new 10 do
     10.times do |value|
     ticketNum=15
     buyTicket(ticketNum)
     sleep 0.01
     fine
fine
 
ticket2=Thread.new 10 do
     10.times do |value|
     ticketNum=20
     buyTicket(ticketNum)
     sleep 0.01
     fine
fine
 
sleep 1
ticket1.join
ticket2.join

Il risultato dell'esecuzione del codice sopra è:

Sincronizzare il Thread
hai acquistato con successo 15 biglietti
hai acquistato con successo 20 biglietti
hai acquistato con successo 15 biglietti
hai acquistato con successo 20 biglietti
hai acquistato con successo 15 biglietti
hai acquistato con successo 20 biglietti
hai acquistato con successo 15 biglietti
hai acquistato con successo 20 biglietti
hai acquistato con successo 15 biglietti
hai acquistato con successo 20 biglietti
hai acquistato con successo 15 biglietti
scusate, non ci sono abbastanza biglietti
scusate, non ci sono abbastanza biglietti
scusate, non ci sono abbastanza biglietti
scusate, non ci sono abbastanza biglietti
scusate, non ci sono abbastanza biglietti
scusate, non ci sono abbastanza biglietti
scusate, non ci sono abbastanza biglietti
scusate, non ci sono abbastanza biglietti
scusate, non ci sono abbastanza biglietti

Oltre all'uso di lock per bloccare la variabile, è possibile utilizzare try_lock per bloccare la variabile, o utilizzare Mutex.synchronize per sincronizzare l'accesso a un variabile.

La classe Queue che regola la consegna dei dati realizza la sincronizzazione dei thread

La classe Queue rappresenta una coda che supporta i thread, che può accedere in modo sincronizzato alla fine della coda. Diversi thread possono utilizzare la stessa istanza della classe, ma non devono preoccuparsi della sincronizzazione dei dati nella coda. Inoltre, l'uso della classe SizedQueue può limitare la lunghezza della coda

La classe SizedQueue può aiutarci molto nel sviluppo di applicazioni sincronizzate tra thread, poiché una volta aggiunta a questa coda, non dobbiamo preoccuparci della sincronizzazione dei thread.

Problema classico del produttore-consumatore:

Esempio online

#!/usr/bin/ruby
 
require "thread"
puts "SizedQuee Test"
 
queue = Queue.new
 
producer = Thread.new do
     10.times do |i|
          sleep rand(i) # far dormire il thread per un periodo di tempo
          queue << i
          puts "#{i} prodotti"
     fine
fine
 
consumer = Thread.new do
     10.times do |i|
          value = queue.pop
          sleep rand(i/2)
          puts "consumed #{value}"
     fine
fine
 
consumer.join

Output del programma:

SizedQuee Test
0 prodotti
1 prodotto
consumed 0
2 prodotti
consumed 1
consumed 2
3 prodotti
consumed 34 prodotti
consumed 4
5 prodotti
consumed 5
6 prodotti
consumed 6
7 prodotti
consumed 7
8 prodotti
9 prodotti
consumed 8
consumed 9

Variabili del thread

Le thread può avere variabili private, le variabili private del thread vengono scritte nel momento in cui il thread viene creato. Possono essere utilizzate all'interno del contesto del thread, ma non possono essere condivise al di fuori del contesto del thread.

Ma a volte, cosa si deve fare se le variabili locali dei thread devono essere accedute da altri thread o dal thread principale? Ruby fornisce la possibilità di creare variabili di thread tramite nome, simile a considerare i thread come una tabella hash. Scrivi dati utilizzando []= e leggi dati utilizzando []. Vediamo il seguente codice:

Esempio online

#!/usr/bin/ruby
 
count = 0
arr = []
 
10.times do |i|
   arr[i] = Thread.new {
      sleep(rand(0)/10.0)
      Thread.current["mycount"] = count
      count += 1
   }
fine
 
arr.each {|t| t.join; print t["mycount"], ", "}
puts "count = #{count}"

Il risultato dell'esecuzione del codice sopra è:

8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10

Il thread principale attende che il thread secondario completi l'esecuzione e poi esce ciascun valore. 。

Priorità del thread

La priorità del thread è un fattore principale che influisce sulla pianificazione dei thread. Altri fattori includono la lunghezza del tempo di esecuzione utilizzato dal CPU, la pianificazione di gruppo dei thread, ecc.

Puoi ottenere la priorità di un thread utilizzando il metodo Thread.priority e modificarla utilizzando il metodo Thread.priority=.

La priorità predefinita dei thread è 0. I thread con priorità più alta eseguono più rapidamente.

Un thread può accedere a tutti i dati nel proprio ambito, ma cosa si deve fare se si ha bisogno di accedere ai dati di un altro thread all'interno di un thread? La classe Thread fornisce metodi per l'accesso reciproco dei dati dei thread, puoi semplicemente considerare un thread come una tabella hash, puoi scrivere dati utilizzando []= in qualsiasi thread e leggere dati utilizzando [].

athr = Thread.new { Thread.current["name"] = "Thread A"; Thread.stop }
bthr = Thread.new { Thread.current["name"] = "Thread B"; Thread.stop }
cthr = Thread.new { Thread.current["name"] = "Thread C"; Thread.stop }
Thread.list.each {|x| puts "#{x.inspect}: #{x["name"]}"}

Si può vedere che, considerando i thread come una tabella hash, utilizzando i metodi [] e []=, abbiamo implementato la condivisione dei dati tra i thread.

Mutual exclusion tra thread

Mutex (Mutual Exclusion = blocco di esclusione) è un meccanismo utilizzato nel programmazione multithreading per prevenire che due thread scrivano o leggano contemporaneamente la stessa risorsa comune (ad esempio, una variabile globale).

Esempio senza Mutax

Esempio online

#!/usr/bin/ruby
require 'thread'
 
count1 = count2 = 0
difference = 0
counter = Thread.new do
   loop do
      count1 += 1
      count2 += 1
   fine
fine
spy = Thread.new do
   loop do
      difference += (count1 - count2).abs
   fine
fine
sleep 1
puts "count1 : #{count1}"
puts "count2 : #{count2}"
puts "difference : #{difference}"

Esempio di output di esecuzione sopra:

count1 : 9712487
count2 : 12501239
difference : 0

Esempio di utilizzo di mutex

Esempio online

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
 
count1 = count2 = 0
difference = 0
counter = Thread.new do
   loop do
      mutex.synchronize do
         count1 += 1
         count2 += 1
      fine
    fine
fine
spy = Thread.new do
   loop do
       mutex.synchronize do
          difference += (count1 - count2).abs
       fine
   fine
fine
sleep 1
mutex.lock
puts "count1 : #{count1}"
puts "count2 : #{count2}"
puts "difference : #{difference}"

Esempio di output di esecuzione sopra:

count1 : 1336406
count2 : 1336406
difference : 0

Blocco morto

Quando due o più unità di calcolo si aspettano che l'altra smetta di funzionare per ottenere risorse di sistema, ma nessuna delle due si ritira prima, questa situazione si chiama blocco morto.

Ad esempio, un processo p1 utilizza il display e deve utilizzare anche la stampante, ma la stampante è occupata dal processo p2, che deve utilizzare il display, formando così un blocco morto.

Quando utilizziamo l'oggetto Mutex dobbiamo prestare attenzione al blocco morto delle thread.

Esempio online

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
 
cv = ConditionVariable.new
a = Thread.new {
   mutex.synchronize {
      puts "A: Ho la sezione critica, ma aspetterò cv"
      cv.wait(mutex)
      puts "A: Ho di nuovo la sezione critica! Regno!"
   }
}
 
puts "(Più tardi, tornato alla fattoria...)"
 
b = Thread.new {
   mutex.synchronize {
      puts "B: Ora sono critico, ma ho finito con cv"
      cv.signal
      puts "B: Sono ancora in sezione critica, sto finendo"
   }
}
a.join
b.join

L'output di esempio sopra è il seguente:

A: Ho la sezione critica, ma aspetterò cv
(Più tardi, tornato alla fattoria...)
B: Ora sono in sezione critica, ma ho finito con cv
B: Sono ancora in sezione critica, sto finendo
A: Ho di nuovo la sezione critica! Regno!

Metodi della classe thread

I metodi della classe completa Thread (thread) sono i seguenti:

NumeroDescrizione del metodo
1Thread.abort_on_exception
Se il valore è vero, l'intero interpretatore viene interrotto una volta che un thread si è fermato a causa di un'eccezione. Il suo valore predefinito è falso, il che significa che, in condizioni normali, se un thread si ferma per un'eccezione e l'eccezione non viene rilevata da Thread#join o altri meccanismi di rilevamento, il thread viene terminato senza avvisi.
2Thread.abort_on_exception=
Se impostato a trueUna volta che un thread si è fermato a causa di un'eccezione, l'intero interpretatore viene interrotto. Restituisce lo stato nuovo
3Thread.critical
Restituisce un valore booleano.
4Thread.critical=
Quando il valore è true, non viene eseguito il passaggio di thread. Se il thread corrente è sospeso (stop) o viene interrotto da un segnale (signal), il valore diventa automaticamente false.
5Thread.current
Restituisce il thread in esecuzione (thread corrente).
6Thread.exit
Termina l'esecuzione del thread corrente. Restituisce il thread corrente. Se il thread corrente è l'unico thread, utilizza exit(0) per terminare la sua esecuzione.
7Thread.fork { block }
Genera un thread come Thread.new.
8Thread.kill( aThread )
Termina l'esecuzione del thread.
9Thread.list
Restituisce un array di thread in esecuzione o in stato sospeso.
10Thread.main
Restituisce il thread principale.
11Thread.new( [ arg ]* ) {| args | block }
Crea un thread e inizia a eseguire. I valori vengono passati in blocco al thread. Questo permette di passare valori alle variabili locali intrinseche del thread all'avvio del thread.
12Thread.pass
Passa il controllo di esecuzione a un altro thread. Non cambia lo stato del thread in esecuzione, ma passa il controllo a un altro thread eseguibile (scheduling thread esplicito).
13Thread.start( [ args ]* ) {| args | block }
Crea un thread e inizia a eseguire. I valori vengono passati in blocco al thread. Questo permette di passare valori alle variabili locali intrinseche del thread all'avvio del thread.
14Thread.stop
Sospende il thread corrente fino a quando un altro thread non lo risveglia utilizzando il metodo run.

Metodi di esempio di thread

Di seguito è riportato un esempio che chiama il metodo di esempio di thread join:

Esempio online

#!/usr/bin/ruby
 
thr = Thread.new do # Esempio
   puts "Nel thread secondario"
   solleva "Solleva eccezione"
fine
thr.join # Esempio di chiamata del metodo di esempio join

Di seguito è elencata la lista completa dei metodi di esempio:

NumeroDescrizione del metodo
1thr[ nome ]
Estrae i dati intrinseci del thread corrispondenti al nome. name può essere una stringa o un simbolo. Restituisce nil se non ci sono dati corrispondenti al nome.
2thr[ nome ] =
Imposta il valore dei dati intrinseci del thread corrispondenti al nome, name può essere una stringa o un simbolo. Se impostato su nil, verrà eliminato il dato corrispondente nel thread.
3thr.abort_on_exception
Restituisce un valore booleano.
4thr.abort_on_exception=
Se il valore è true, l'intero interprete viene interrotto non appena un thread termina per eccezione.
5thr.alive?
Restituisce true se il thread è "vivo".
6thr.exit
Termina l'esecuzione del thread. Restituisce self.
7thr.join
Sospende il thread corrente fino a quando il thread self non termina l'esecuzione. Se self termina per eccezione, il thread corrente solleverà la stessa eccezione.
8thr.key?
Restituisce true se i dati intrinseci del thread corrispondenti al nome sono già definiti.
9thr.kill
Simile a Thread.exit
10thr.priority
Restituisce la priorità del thread. Il valore predefinito della priorità è 0. Più grande è questo valore, più alta è la priorità.
11thr.priority=
Imposta la priorità del thread. Può essere impostata anche su un numero negativo.
12thr.raise( unEccezione )
Solleva un'eccezione forzata all'interno del thread.
13thr.run
Riavvia il thread sospeso (stop). A differenza di wakeup, esegue immediatamente la transizione del thread. Se si utilizza questo metodo su un processo morto, verrà sollevata l'eccezione ThreadError.
14thr.safe_level
Restituisce il livello di sicurezza di self. Il livello di safe_level del thread corrente è lo stesso di $SAFE.
15thr.status
Usa le stringhe "run", "sleep" o "aborting" per rappresentare lo stato di un thread vivo. Se un thread termina normalmente, restituisce false. Se termina a causa di un'eccezione, restituisce nil.
16thr.stop?
Restituisce true se il thread è in stato terminato (dead) o sospeso (stop).
17thr.value
Aspetta che il thread self si fermi (equivalente a join) e torna il valore del blocco del thread. Se durante l'esecuzione del thread si verifica un'eccezione, verrà sollevata nuovamente quell'eccezione.
18thr.wakeup
Cambia lo stato di un thread sospeso (stop) in uno stato eseguibile (run). Se si esegue questo metodo su un thread morto, verrà sollevata un'eccezione ThreadError.