Multithreading Ruby


Chaque programme exécuté sur le système est un processus. Chaque processus contient un ou plusieurs threads.

Un thread est un processus de contrôle séquentiel unique dans un programme. L'exécution de plusieurs threads en même temps pour effectuer différentes tâches dans un seul programme est appelée multi-threading.

Dans Ruby, nous pouvons créer des multi-threads via la classe Thread. Le thread de Ruby est léger et peut implémenter du code parallèle de manière efficace.


Créer un fil Ruby

Pour démarrer un nouveau fil de discussion, appelez simplement Thread.new :

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

Instance

L'exemple suivant montre comment utiliser le multi-threading dans les programmes Ruby :

#!/usr/bin/ruby

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

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

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


Le résultat de l'exécution du code ci-dessus est :

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

Cycle de vie du thread

1 , Les threads peuvent être créés à l'aide de Thread.new, et les threads peuvent également être créés à l'aide des trois méthodes Thread.start ou Thread.fork avec la même syntaxe.

2. Il n'est pas nécessaire de démarrer le fil après l'avoir créé, le fil s'exécutera automatiquement.

3. La classe Thread définit certaines méthodes pour contrôler les threads. Le thread exécute le bloc de code dans Thread.new.

4. La dernière instruction du bloc de code du thread est la valeur du thread, qui peut être appelée via la méthode thread. Si l'exécution du thread est terminée, la valeur du thread sera renvoyée, sinon la valeur ne sera pas renvoyée. renvoyé jusqu'à ce que l'exécution du thread soit terminée.

5. La méthode Thread.current renvoie un objet représentant le thread actuel. La méthode Thread.main renvoie le thread principal.

6. Exécutez le thread via la méthode Thread.Join. Cette méthode suspendra le thread principal jusqu'à ce que le thread actuel termine son exécution.


Statut du fil de discussion

Les fils de discussion ont 5 états :

线程状态返回值
可执行run
睡眠Sleeping
退出aborting
正常终止false
发生异常终止nil

Threads et exceptions

Lorsqu'une exception se produit dans un thread et n'est pas interceptée par le sauvetage, le thread sera généralement terminé sans avertissement. Cependant, si d'autres threads attendaient ce thread en raison de Thread#join, le thread en attente recevra également la même exception.

begin
  t = Thread.new do
    Thread.pass    # 主线程确实在等join
    raise "unhandled exception"
  end
  t.join
rescue
  p $!  # => "unhandled exception"
end

Utilisez les 3 méthodes suivantes pour permettre à l'interpréteur de s'interrompre lorsqu'un thread se termine en raison d'une exception.

  • Spécifiez l'option -d lors du démarrage du script et exécutez-le en mode débogage.

  • Placez le drapeau avec Thread.abort_on_exception.

  • Utilisez Thread#abort_on_exception pour définir l'indicateur pour le fil de discussion spécifié.

Lorsque l'une des 3 méthodes ci-dessus est utilisée, l'intégralité de l'interprète sera interrompue.

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

Contrôle de synchronisation des threads

Dans Ruby, trois façons d'effectuer la synchronisation sont proposées, à savoir :

1. Synchronisation des threads via la classe Mutex

2. La classe Queue qui supervise le transfert de données réalise la synchronisation des threads

3. Utilisez ConditionVariable pour réaliser le contrôle de synchronisation

La synchronisation des threads est réalisée via la classe Mutex

C'est le cas. réalisé via le contrôle de synchronisation des threads de la classe Mutex, si une variable de programme est nécessaire dans plusieurs threads en même temps, cette variable peut être partiellement verrouillée à l'aide d'un verrou. Le code est le suivant :

#!/usr/bin/ruby

require "thread"
puts "Synchronize Thread"

@num=200
@mutex=Mutex.new

def buyTicket(num)
     @mutex.lock
          if @num>=num
               @num=@num-num
               puts "you have successfully bought #{num} tickets"
          else
               puts "sorry,no enough tickets"
          end
     @mutex.unlock
end

ticket1=Thread.new 10 do
     10.times do |value|
     ticketNum=15
     buyTicket(ticketNum)
     sleep 0.01
     end
end

ticket2=Thread.new 10 do
     10.times do |value|
     ticketNum=20
     buyTicket(ticketNum)
     sleep 0.01
     end
end

sleep 1
ticket1.join
ticket2.join


Le résultat de l'exécution du code ci-dessus est :

Synchronize Thread
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
you have successfully bought 20 tickets
you have successfully bought 15 tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets
sorry,no enough tickets

En plus d'utiliser le verrouillage pour verrouiller les variables, vous pouvez également utilisez try_lock pour verrouiller les variables, et vous pouvez également utiliser Mutex synchronise l'accès à une variable.

La classe Queue qui supervise le transfert de données implémente la synchronisation des threads

La classe Queue représente une file d'attente qui prend en charge les threads et peut accéder de manière synchrone à la fin de la file d'attente. Différents threads peuvent utiliser la même classe de paire, mais vous n'avez pas à vous soucier de savoir si les données de cette file d'attente peuvent être synchronisées. De plus, l'utilisation de la classe SizedQueue peut limiter la longueur de la file d'attente

The SizedQueue. La classe peut très facilement nous aider à développer des applications de synchronisation de threads. Le programme ne devrait pas avoir à se soucier de la synchronisation des threads tant qu'il rejoint cette file d'attente.

Problème classique producteur-consommateur :

#!/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"
     end
end

consumer = Thread.new do
     10.times do |i|
          value = queue.pop
          sleep rand(i/2)
          puts "consumed #{value}"
     end
end

consumer.join


Sortie du programme :

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

Variable de thread

Les threads peuvent avoir leurs propres variables privées. Les variables privées du thread sont écrites dans le thread lors de la création du thread. Peut être utilisé dans la portée du thread, mais ne peut pas être partagé en dehors du thread.

Mais parfois, que dois-je faire si les variables locales d'un thread doivent être accessibles par d'autres threads ou par le thread principal ? Ruby fournit un moyen de créer des variables de thread par nom, similaire au traitement des threads comme des tables de hachage. Écrivez les données via []= et lisez les données via []. Jetons un coup d'œil au code suivant :

#!/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
   }
end

arr.each {|t| t.join; print t["mycount"], ", " }
puts "count = #{count}"

Le résultat de sortie du code ci-dessus est :

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

Le thread principal attend que le sous-thread termine son exécution, puis génère chaque valeur. séparément. .


Priorité des threads

La priorité d'un thread est le principal facteur qui affecte la planification des threads. D'autres facteurs incluent la durée d'exécution occupée par le processeur, la planification du regroupement de threads, etc.

Vous pouvez utiliser la méthode Thread.priority pour obtenir la priorité du fil et la méthode Thread.priority= pour ajuster la priorité du fil.

La priorité du thread est de 0 par défaut. Ceux qui ont une priorité plus élevée s’exécutent plus rapidement.

Un thread peut accéder à toutes les données dans sa propre portée, mais que devez-vous faire si vous avez besoin d'accéder aux données d'autres threads dans un thread ? La classe Thread fournit une méthode permettant aux données de thread d'accéder les unes aux autres. Vous pouvez simplement utiliser un thread comme table de hachage. Vous pouvez utiliser []= pour écrire des données et [] pour lire des données dans n'importe quel thread.

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

Comme vous pouvez le voir, en traitant le thread comme une table de hachage et en utilisant les méthodes [] et []=, nous réalisons le partage de données entre les threads.


Exclusion mutuelle de threads

Mutex (Mutal Exclusion = mutex lock) est un type de verrou utilisé dans la programmation multi-thread pour empêcher deux threads d'accéder à la même ressource publique (comme variables globales) en même temps) mécanisme de lecture et d'écriture.

Instance sans Mutax

#!/usr/bin/ruby
require 'thread'

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

Le résultat de sortie de l'instance ci-dessus est :

count1 :  9712487
count2 :  12501239
difference : 0

Instance utilisant Mutax

#!/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
      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}"

Le résultat de sortie de l'instance ci-dessus est :

count1 :  1336406
count2 :  1336406
difference : 0

Deadlock

Deux unités de calcul ou plus, les deux parties attendent que l'autre arrête de fonctionner pour obtenir des ressources système, mais lorsqu'aucune des parties ne sort plus tôt, cette situation est C'est appelé une impasse.

Par exemple, un processus p1 occupe le moniteur et doit utiliser l'imprimante en même temps, et l'imprimante est occupée par le processus p2, et p2 doit utiliser le moniteur, formant ainsi une impasse.

Nous devons faire attention au blocage des threads lors de l'utilisation d'objets Mutex.

Instance

#!/usr/bin/ruby
require 'thread'
mutex = Mutex.new

cv = ConditionVariable.new
a = Thread.new {
   mutex.synchronize {
      puts "A: I have critical section, but will wait for cv"
      cv.wait(mutex)
      puts "A: I have critical section again! I rule!"
   }
}

puts "(Later, back at the ranch...)"

b = Thread.new {
   mutex.synchronize {
      puts "B: Now I am critical, but am done with cv"
      cv.signal
      puts "B: I am still critical, finishing up"
   }
}
a.join
b.join

Le résultat de sortie de l'exemple ci-dessus est :

A: I have critical section, but will wait for cv
(Later, back at the ranch...)
B: Now I am critical, but am done with cv
B: I am still critical, finishing up
A: I have critical section again! I rule!

Méthode de classe Thread

La méthode de classe Thread complète est la suivante :

序号方法描述
1Thread.abort_on_exception
若其值为真的话,一旦某线程因异常而终止时,整个解释器就会被中断。它的默认值是假,也就是说,在通常情况下,若某线程发生异常且该异常未被Thread#join等检测到时,该线程会被无警告地终止。
2Thread.abort_on_exception=
如果设置为 true, 一旦某线程因异常而终止时,整个解释器就会被中断。返回新的状态
3Thread.critical
返回布尔值。
4Thread.critical=
当其值为true时,将不会进行线程切换。若当前线程挂起(stop)或有信号(signal)干预时,其值将自动变为false。
5Thread.current
返回当前运行中的线程(当前线程)。
6Thread.exit
终止当前线程的运行。返回当前线程。若当前线程是唯一的一个线程时,将使用exit(0)来终止它的运行。
7Thread.fork { block }
与 Thread.new 一样生成线程。
8Thread.kill( aThread )
终止线程的运行.
9Thread.list
返回处于运行状态或挂起状态的活线程的数组。
10Thread.main
返回主线程。
11Thread.new( [ arg ]* ) {| args | block }
生成线程,并开始执行。数会被原封不动地传递给块. 这就可以在启动线程的同时,将值传递给该线程所固有的局部变量。
12Thread.pass
将运行权交给其他线程. 它不会改变运行中的线程的状态,而是将控制权交给其他可运行的线程(显式的线程调度)。
13Thread.start( [ args ]* ) {| args | block }
生成线程,并开始执行。数会被原封不动地传递给块. 这就可以在启动线程的同时,将值传递给该线程所固有的局部变量。
14Thread.stop
将当前线程挂起,直到其他线程使用run方法再次唤醒该线程。

Méthode d'instanciation de thread

L'instance suivante appelle la méthode d'instanciation de thread join :

#!/usr/bin/ruby

thr = Thread.new do   # 实例化
   puts "In second thread"
   raise "Raise exception"
end
thr.join   # 调用实例化方法 join

Voici la liste complète des méthodes d'instanciation :

序号方法描述
1thr[ name ]
取出线程内与name相对应的固有数据。 name可以是字符串或符号。 若没有与name相对应的数据时, 返回nil。
2thr[ name ] =
设置线程内name相对应的固有数据的值, name可以是字符串或符号。 若设为nil时, 将删除该线程内对应数据。
3thr.abort_on_exception
返回布尔值。
4thr.abort_on_exception=
若其值为true的话,一旦某线程因异常而终止时,整个解释器就会被中断。
5thr.alive?
若线程是"活"的,就返回true。
6thr.exit
终止线程的运行。返回self。
7thr.join
挂起当前线程,直到self线程终止运行为止. 若self因异常而终止时, 将会当前线程引发同样的异常。
8thr.key?
若与name相对应的线程固有数据已经被定义的话,就返回true
9thr.kill
类似于 Thread.exit
10thr.priority
返回线程的优先度. 优先度的默认值为0. 该值越大则优先度越高.
11thr.priority=
设定线程的优先度. 也可以将其设定为负数.
12thr.raise( anException )
在该线程内强行引发异常.
13thr.run
重新启动被挂起(stop)的线程. 与wakeup不同的是,它将立即进行线程的切换. 若对死进程使用该方法时, 将引发ThreadError异常.
14thr.safe_level
返回self 的安全等级. 当前线程的safe_level与$SAFE相同.
15thr.status
使用字符串"run"、"sleep"或"aborting" 来表示活线程的状态. 若某线程是正常终止的话,就返回false. 若因异常而终止的话,就返回nil。
16thr.stop?
若线程处于终止状态(dead)或被挂起(stop)时,返回true.
17thr.value
一直等到self线程终止运行(等同于join)后,返回该线程的块的返回值. 若在线程的运行过程中发生了异常, 就会再次引发该异常.
18thr.wakeup
把被挂起(stop)的线程的状态改为可执行状态(run), 若对死线程执行该方法时,将会引发ThreadError异常。