English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
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.
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
A seguinte exemplo mostra como usar threads em programas Ruby:
#!/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
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.
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 thread | valor de retorno |
---|---|
executável | executar |
dormir | Dormindo |
saindo | abortando |
Terminação normal | false |
Terminação por exceção | nil |
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
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 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:
#!/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类就是表示一个支持线程的队列,能够同步对队列末尾进行访问。不同的线程可以使用统一个对类,但是不用担心这个队列中的数据是否能够同步,另外使用SizedQueue类能够限制队列的长度
SizedQueue类能够非常便捷的帮助我们开发线程同步的应用程序,应为只要加入到这个队列中,就不用关心线程的同步问题。
经典的生产者消费者问题:
#!/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
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.
#!/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. 。
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.
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.
#!/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
#!/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
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.
#!/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!
Os métodos completos da classe Thread (thread) são os seguintes:
Número | Descrição do método |
---|---|
1 | Thread.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. |
2 | Thread.abort_on_exception= Se definido como trueQuando uma thread for terminada por exceção, todo o interpretador será interrompido. Retorna o novo estado |
3 | Thread.critical Retornar valor booleano. |
4 | Thread.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. |
5 | Thread.current Retorna a thread em execução (a thread atual). |
6 | Thread.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. |
7 | Thread.fork { block } Gera uma thread, como o Thread.new. |
8 | Thread.kill( aThread ) Termina a execução da thread. |
9 | Thread.list Retorna um array de threads ativas em estado de execução ou suspenso. |
10 | Thread.main Retorna a thread principal. |
11 | Thread.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. |
12 | Thread.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). |
13 | Thread.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. |
14 | Thread.stop Suspender a thread atual até que outra thread use o método run para acordar a thread novamente. |
A seguir, o exemplo chamou o método exemplificado da thread join:
#!/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úmero | Descrição do método |
---|---|
1 | thr[ 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. |
2 | thr[ 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. |
3 | thr.abort_on_exception Retornar valor booleano. |
4 | thr.abort_on_exception= Se o valor for true, a interpretação inteira será interrompida assim que uma thread for encerrada por exceção. |
5 | thr.alive? Se a thread estiver "ativa", retorne true. |
6 | thr.exit Terminar a execução da thread. Retornar self. |
7 | thr.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. |
8 | thr.key? Se os dados inatos do thread correspondentes ao nome já foram definidos, retorne true |
9 | thr.kill Semelhante a Thread.exit 。 |
10 | thr.priority Retornar a prioridade da thread. O valor padrão da prioridade é 0. Quanto maior o valor, maior a prioridade. |
11 | thr.priority= Definir a prioridade da thread. Também pode ser definida como um número negativo. |
12 | thr.raise( anException ) Forçar a geração de exceção dentro dessa thread. |
13 | thr.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. |
14 | thr.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. |
15 | thr.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. |
16 | thr.stop? Retorna true se a thread estiver no estado de terminação (dead) ou suspensa (stop). |
17 | thr.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. |
18 | thr.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. |