English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。
线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。
Ruby 中我们可以通过 Thread 类来创建多线程,Ruby的线程是一个轻量级的,可以以高效的方式来实现并行的代码。
要启动一个新的线程,只需要调用 Thread.new 即可:
# 线程 #1 代码部分 Thread.new { # 线程 #2 执行代码 } # 线程 #1 执行代码
以下示例展示了如何在Ruby程序中使用多线程:
#!/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
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.
There are 5 states of the thread:
thread status | return value |
---|---|
executable | run |
sleeping | Sleeping |
exit | aborting |
Normal termination | false |
Exception termination occurred | nil |
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
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, se più thread devono accedere contemporaneamente a una variabile di programma, questa parte può essere bloccata con lock. Il codice è il seguente:
#!/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 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:
#!/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
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:
#!/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. 。
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.
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).
#!/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
#!/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
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.
#!/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!
I metodi della classe completa Thread (thread) sono i seguenti:
Numero | Descrizione del metodo |
---|---|
1 | Thread.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. |
2 | Thread.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 |
3 | Thread.critical Restituisce un valore booleano. |
4 | Thread.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. |
5 | Thread.current Restituisce il thread in esecuzione (thread corrente). |
6 | Thread.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. |
7 | Thread.fork { block } Genera un thread come Thread.new. |
8 | Thread.kill( aThread ) Termina l'esecuzione del thread. |
9 | Thread.list Restituisce un array di thread in esecuzione o in stato sospeso. |
10 | Thread.main Restituisce il thread principale. |
11 | Thread.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. |
12 | Thread.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). |
13 | Thread.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. |
14 | Thread.stop Sospende il thread corrente fino a quando un altro thread non lo risveglia utilizzando il metodo run. |
Di seguito è riportato un esempio che chiama il metodo di esempio di thread join:
#!/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:
Numero | Descrizione del metodo |
---|---|
1 | thr[ 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. |
2 | thr[ 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. |
3 | thr.abort_on_exception Restituisce un valore booleano. |
4 | thr.abort_on_exception= Se il valore è true, l'intero interprete viene interrotto non appena un thread termina per eccezione. |
5 | thr.alive? Restituisce true se il thread è "vivo". |
6 | thr.exit Termina l'esecuzione del thread. Restituisce self. |
7 | thr.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. |
8 | thr.key? Restituisce true se i dati intrinseci del thread corrispondenti al nome sono già definiti. |
9 | thr.kill Simile a Thread.exit 。 |
10 | thr.priority Restituisce la priorità del thread. Il valore predefinito della priorità è 0. Più grande è questo valore, più alta è la priorità. |
11 | thr.priority= Imposta la priorità del thread. Può essere impostata anche su un numero negativo. |
12 | thr.raise( unEccezione ) Solleva un'eccezione forzata all'interno del thread. |
13 | thr.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. |
14 | thr.safe_level Restituisce il livello di sicurezza di self. Il livello di safe_level del thread corrente è lo stesso di $SAFE. |
15 | thr.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. |
16 | thr.stop? Restituisce true se il thread è in stato terminato (dead) o sospeso (stop). |
17 | thr.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. |
18 | thr.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. |