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

Multithreading Ruby

Cada programa em execução no sistema é um processo. Cada processo contém um a vários threads.

Uma thread é um único fluxo de controle sequencial em um programa, e ao mesmo tempo, várias threads podem ser executadas em um único programa para realizar diferentes tarefas, o que é conhecido como multi-threading.

No Ruby, podemos criar threads usando a classe Thread. As threads no Ruby são leves e podem ser implementadas de maneira eficiente para realizar código paralelo.

Criar thread Ruby

Para iniciar uma nova thread, basta chamar Thread.new:

# Thread #1 Parte do código
Thread.new {
  # Thread #2 Executar código
}
# Thread #1 Executar código

Exemplo em linha

A seguinte exemplo mostra como usar threads em programas Ruby:

Exemplo em linha

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

O resultado da execução do código acima é:

Iniciado em Wed May 14 08:21:54 -0700 2014
func1  14 08:21:54 -0700 2014
func2  14 08:21:54 -0700 2014
func2  14 08:21:55 -0700 2014
func1  14 08:21:56 -0700 2014
func2  14 08:21:56 -0700 2014
func1  14 08:21:58 -0700 2014
at: Wed May 14 08:22 -0700 2014

Fim em Wed May

1:00

2Ciclo de vida da thread

3Thread.new método cria uma thread. Pode usar Thread.start ou Thread.fork usando a mesma sintaxe para criar threads.

4Thread classe define alguns métodos para manipular threads. A thread executa o bloco de código no Thread.new.

5Thread.last método retorna o valor da thread. Se a thread for concluída, retornará o valor da thread, caso contrário, não retornará até que a thread seja concluída.

6Thread.current método retorna o objeto que representa a thread atual. Thread.main método retorna a thread principal.

estado da thread

Through o método Thread.Join para executar a thread, que suspenderá a thread principal até que a thread atual seja concluída.5Existem

estado da threadvalor de retorno
executávelexecutar
dormirDormindo
saindoabortando
Terminação normalfalse
Terminação por exceçãonil

Threads e exceções

Quando uma thread ocorre uma exceção e não é capturada por rescue, a thread geralmente é terminada sem aviso. Mas, se outras threads estiverem esperando essa thread devido a Thread#join, essas threads também serão levadas a mesma exceção.

begin
  t = Thread.new do
    Thread.pass # A thread principal realmente está esperando join
    raise "exceção não tratada"
  fim
  t.join
rescue
  p $! # => "exceção não tratada"
fim

Usar o seguinte3Essa maneira, o interpretador pode ser interrompido quando uma thread termina devido a uma exceção.

  • Especificar ao inicializar o script.-dOpção, e execute com modo de depuração.

  • Usar Thread.abort_on_exception para definir o sinal.

  • Usar Thread#abort_on_exception para definir o sinal para a thread especificada.

Quando usar o seguinte3Após uma dessas maneiras, o interpretador inteiro será interrompido.

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

Controle de sincronização de threads

No Ruby, há três maneiras de implementar a sincronização,分别是:

1Sincronização de threads através da classe Mutex

2. Regulando a troca de dados da classe Queue para implementar a sincronização de threads

3. Usar ConditionVariable para implementar o controle de sincronização

Através da classe Mutex, é possível implementar o controle de sincronização

Através da classe Mutex, é possível implementar o controle de sincronização de threads, se vários threads precisam de um programa variável ao mesmo tempo, pode-se usar a parte da variável lock. O código é o seguinte:

Exemplo em linha

#!/usr/bin/ruby
 
require "thread"
puts "Sincronizar Thread"
 
@num=200
@mutex=Mutex.new
 
def comprarBilhete(número)
     @mutex.lock
          if @num>=número
               @num=@num-número
               puts "você comprou com sucesso #{número} bilhetes"
          else
               puts "sinto, não há bilhetes suficientes"
          fim
     @mutex.unlock
fim
 
bilhete1=Thread.new 10 do
     10.times do |valor|
     númeroDoBilhete=15
     comprarBilhete(númeroDoBilhete)
     sleep 0.01
     fim
fim
 
bilhete2=Thread.new 10 do
     10.times do |valor|
     númeroDoBilhete=20
     comprarBilhete(númeroDoBilhete)
     sleep 0.01
     fim
fim
 
sleep 1
bilhete1.join
bilhete2.join

O resultado da execução do código acima é:

Sincronizar Thread
você comprou com sucesso 15 bilhetes
você comprou com sucesso 20 bilhetes
você comprou com sucesso 15 bilhetes
você comprou com sucesso 20 bilhetes
você comprou com sucesso 15 bilhetes
você comprou com sucesso 20 bilhetes
você comprou com sucesso 15 bilhetes
você comprou com sucesso 20 bilhetes
você comprou com sucesso 15 bilhetes
você comprou com sucesso 20 bilhetes
você comprou com sucesso 15 bilhetes
sinto, não há bilhetes suficientes
sinto, não há bilhetes suficientes
sinto, não há bilhetes suficientes
sinto, não há bilhetes suficientes
sinto, não há bilhetes suficientes
sinto, não há bilhetes suficientes
sinto, não há bilhetes suficientes
sinto, não há bilhetes suficientes
sinto, não há bilhetes suficientes

除了使用lock锁定变量,还可以使用try_lock锁定变量,还可以使用Mutex.synchronize同步对某一个变量的访问。

监管数据交接的Queue类实现线程同步

Queue类就是表示一个支持线程的队列,能够同步对队列末尾进行访问。不同的线程可以使用统一个对类,但是不用担心这个队列中的数据是否能够同步,另外使用SizedQueue类能够限制队列的长度

SizedQueue类能够非常便捷的帮助我们开发线程同步的应用程序,应为只要加入到这个队列中,就不用关心线程的同步问题。

经典的生产者消费者问题:

Exemplo em linha

#!/usr/bin/ruby
 
require "thread"
puts "SizedQuee Test"
 
queue = Queue.new
 
producer = Thread.new do
     10.times do |i|
          sleep rand(i) # 让线程睡眠一段时间
          queue << i
          puts "#{i} produced"
     fim
fim
 
consumer = Thread.new do
     10.times do |i|
          value = queue.pop
          sleep rand(i/2)
          puts "consumed #{value}"
     fim
fim
 
consumer.join

Saída do programa:

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

Variáveis de thread

As threads podem ter variáveis privadas, as variáveis privadas da thread são escritas na criação da thread. Podem ser usadas dentro do escopo da thread, mas não podem ser compartilhadas fora da thread.

Mas o que fazer quando os variáveis locais de um thread precisam ser acessadas por outro thread ou pela thread principal? O Ruby oferece a capacidade de criar variáveis de thread por nome, semelhante a ver a thread como uma hash-like hash table. Pode usar []= para escrever e [] para ler dados.

Exemplo em linha

#!/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
   }
fim
 
arr.each {|t| t.join; print t["mycount"], "," }
puts "count = #{count}"

O resultado da execução do código acima é:

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

A thread principal espera que a sub-thread complete sua execução e, em seguida, imprime cada valor. 。

Prioridade da thread

A prioridade da thread é um dos principais fatores que influenciam a programação de threads. Outros fatores incluem o tempo de execução da thread no CPU, a programação de grupos de threads, etc.

Você pode obter a prioridade da thread usando o método Thread.priority e ajustar a prioridade usando Thread.priority=.

A prioridade padrão da thread é 0. A prioridade mais alta executa mais rapidamente.

Um Thread pode acessar todos os dados do seu escopo, mas e se precisar acessar dados de outro thread dentro de um thread específico? A classe Thread oferece métodos para acesso mútuo de dados entre threads, você pode simplesmente ver um thread como um hash, onde pode usar []= para escrever dados em qualquer thread e usar [] para ler dados.

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

Pode ser visto que, ao usar a thread como uma tabela hash, utilizando os métodos [] e []=, conseguimos compartilhar dados entre threads.

Exclusão de threads

Mutex (Mutual Exclusion = trava de exclusão mútua) é um mecanismo usado em programação multithread, para evitar que duas threads escrevam ou leiam o mesmo recurso público (como uma variável global) ao mesmo tempo.

Exemplo de não uso de Mutax

Exemplo em linha

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

O resultado da execução do exemplo acima é:

count1 :  9712487
count2 :  12501239
diferença: 0

Exemplo de uso de mutex

Exemplo em linha

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

O resultado da execução do exemplo acima é:

count1 :  1336406
count2 :  1336406
diferença: 0

Bloqueio

Mais de uma unidade de operação, ambas esperando que a outra pare de executar para obter recursos do sistema, mas nenhuma das partes sai antes, esse estado é chamado de bloqueio.

Por exemplo, um processo p1Ocupou o monitor, e também é necessário usar a impressora, que está sendo usada pelo processo p2Ocupação, p2E também é necessário usar o monitor, formando assim um bloqueio.

Quando estamos usando o objeto Mutex, devemos prestar atenção ao bloqueio de threads.

Exemplo em linha

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new
 
cv = ConditionVariable.new
a = Thread.new {
   mutex.synchronize {
      puts "A: Tenho a seção crítica, mas esperarei pela cv"
      cv.wait(mutex)
      puts "A: Tenho novamente a seção crítica! Eu reino!"
   }
}
 
puts "(Mais tarde, de volta à fazenda...)"
 
b = Thread.new {
   mutex.synchronize {
      puts "B: Agora estou na seção crítica, mas estou pronto com cv"
      cv.signal
      puts "B: Ainda estou na seção crítica, finalizando"
   }
}
a.join
b.join

O resultado da saída do exemplo acima é:

A: Tenho a seção crítica, mas vou esperar pelo cv
(Mais tarde, de volta à fazenda...)
B: Agora estou na seção crítica, mas estou pronto com cv
B: Ainda estou na seção crítica, finalizando
A: Tenho novamente a seção crítica! Eu reino!

Métodos da classe Thread

Os métodos completos da classe Thread (thread) são os seguintes:

NúmeroDescrição do método
1Thread.abort_on_exception
Se seu valor for true, ao ser terminada por exceção, todo o interpretador será interrompido. Seu valor padrão é falso, o que significa que, na maioria das vezes, se uma thread ocorrer uma exceção e essa exceção não for detectada por Thread#join, etc., a thread será terminada sem aviso.
2Thread.abort_on_exception=
Se definido como trueQuando uma thread for terminada por exceção, todo o interpretador será interrompido. Retorna o novo estado
3Thread.critical
Retornar valor booleano.
4Thread.critical=
Quando seu valor for true, não será feita troca de threads. Se a thread atual estiver suspensa (stop) ou houver interrupção por sinal (signal), seu valor automaticamente se torna false.
5Thread.current
Retorna a thread em execução (a thread atual).
6Thread.exit
Termina a execução da thread atual. Retorna a thread atual. Se a thread atual for a única, usará exit(0) para terminá-la.
7Thread.fork { block }
Gera uma thread, como o Thread.new.
8Thread.kill( aThread )
Termina a execução da thread.
9Thread.list
Retorna um array de threads ativas em estado de execução ou suspenso.
10Thread.main
Retorna a thread principal.
11Thread.new( [ arg ]* ) {| args | bloco }
Gerar thread e começar a executar. O valor será passado para o bloco sem alteração. Isso permite passar valores para as variáveis locais inatas da thread ao iniciar a thread.
12Thread.pass
Passa o direito de execução para outra thread. Não altera o estado da thread em execução, mas transfere o controle para outra thread executável (agendamento explícito de threads).
13Thread.start( [ args ])* ) {| args | bloco }
Gerar thread e começar a executar. O valor será passado para o bloco sem alteração. Isso permite passar valores para as variáveis locais inatas da thread ao iniciar a thread.
14Thread.stop
Suspender a thread atual até que outra thread use o método run para acordar a thread novamente.

Métodos da thread

A seguir, o exemplo chamou o método exemplificado da thread join:

Exemplo em linha

#!/usr/bin/ruby
 
thr = Thread.new do   # Exemplo
   puts "Na thread segunda"
   raise "Lançar exceção"
fim
thr.join   # Exemplo de chamada de método exemplificado join

A seguir está a lista completa de métodos exemplificados:

NúmeroDescrição do método
1thr[ name ]
Retirar o dado inato correspondente ao nome dentro da thread. O nome pode ser uma string ou um símbolo. Se não houver dados correspondentes ao nome, retorne nil.
2thr[ name ]=
Definir o valor do dado inato correspondente ao nome dentro da thread, onde o nome pode ser uma string ou um símbolo. Se definido como nil, será removido o dado correspondente na thread.
3thr.abort_on_exception
Retornar valor booleano.
4thr.abort_on_exception=
Se o valor for true, a interpretação inteira será interrompida assim que uma thread for encerrada por exceção.
5thr.alive?
Se a thread estiver "ativa", retorne true.
6thr.exit
Terminar a execução da thread. Retornar self.
7thr.join
Suspender a thread atual até que a thread self termine a execução. Se a self for encerrada por exceção, a mesma exceção será gerada na thread atual.
8thr.key?
Se os dados inatos do thread correspondentes ao nome já foram definidos, retorne true
9thr.kill
Semelhante a Thread.exit
10thr.priority
Retornar a prioridade da thread. O valor padrão da prioridade é 0. Quanto maior o valor, maior a prioridade.
11thr.priority=
Definir a prioridade da thread. Também pode ser definida como um número negativo.
12thr.raise( anException )
Forçar a geração de exceção dentro dessa thread.
13thr.run
Reiniciar a thread suspensa (stop). Diferente de wakeup, ele fará a troca de thread imediatamente. Quando esse método é usado em um processo morto, ele provocará a exceção ThreadError.
14thr.safe_level
Retorna o nível de segurança do self. O nível de segurança da thread atual safe_level é o mesmo que $SAFE.
15thr.status
Usa as strings "run", "sleep" ou "aborting" para representar o estado de uma thread viva. Se uma thread for encerrada normalmente, retornará false. Se for encerrada por exceção, retornará nil.
16thr.stop?
Retorna true se a thread estiver no estado de terminação (dead) ou suspensa (stop).
17thr.value
Esperar até que a thread self termine sua execução (equivalente a join) e retornar o valor do bloco da thread. Se ocorrer uma exceção durante a execução da thread, essa exceção será lançada novamente.
18thr.wakeup
Muda o estado da thread suspensa (stop) para o estado executável (run). Se este método for executado em uma thread morta, será gerada uma exceção ThreadError.