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 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}" t1Thread.new{func1()} t2Thread.new{func2()} t1.join t2.join puts "Fin à #{Time.now}"
Le résultat de l'exécution du code suivant est :
Débuté le mer mai 14 08:21:54 -0700 2014 func1 at: mer mai 14 08:21:54 -0700 2014 func2 at: mer mai 14 08:21:54 -0700 2014 func2 at: mer mai 14 08:21:55 -0700 2014 func1 at: mer mai 14 08:21:56 -0700 2014 func2 at: mer mai 14 08:21:56 -0700 2014 func1 at: mer mai 14 08:21:58 -0700 2014 Fin le mer mai 14 08:22:00 -0700 2014
1La création d'un thread peut être réalisée à l'aide de Thread.new, et peut également être réalisée avec les mêmes syntaxes Thread.start ou Thread.fork pour créer un thread.
2Après la création d'un thread, il n'est pas nécessaire de le démarrer, il s'exécute automatiquement.
3La classe Thread définit certaines méthodes pour contrôler les threads. Le code du thread est exécuté dans le bloc de code Thread.new.
4La dernière instruction d'un bloc de code de thread est la valeur du thread, qui peut être appelée par les méthodes du thread. Si le thread s'est terminé, la valeur du thread est retournée, sinon, aucune valeur n'est retournée jusqu'à ce que le thread soit terminé.
5La méthode Thread.current retourne un objet représentant le thread courant. La méthode Thread.main retourne le thread principal.
6Exécuter un thread à l'aide de la méthode Thread.Join, cette méthode suspend le thread principal jusqu'à ce que le thread courant soit terminé.
Un thread peut5Il y a plusieurs états :
État du thread | Valeur de retour |
---|---|
Exécutable | run |
Sommeil | Sleeping |
Sortie | aborting |
Arrêt normal | false |
Arrêt par exception | nil |
Lorsqu'un thread rencontre une exception qui n'est pas capturée par rescue, ce thread est généralement terminé sans avertissement. Cependant, si d'autres threads attendent ce thread en raison de Thread#join, ces threads seront également soumis à la même exception.
begin t = Thread.new do Thread.pass # Le thread principal attend vraiment join raise "exception non gérée" end t.join rescue p $! # => "exception non gérée" end
3En utilisant les instructions suivantes
Cette méthode permet à l'interpréteur de s'arrêter lorsque l'un des threads se termine par une exception.-dEn spécifiant cette option lors du lancement du script.
Définir le drapeau avec Thread.abort_on_exception.
Définir un drapeau pour le thread spécifié en utilisant Thread#abort_on_exception.
Lors de l'utilisation de l'une des options ci-dessus, et du mode débogage lors de l'exécution.3Après l'une de ces méthodes, l'interpréteur est interrompu en totalité.
t = Thread.new { ... } t.abort_on_exception = true
En Ruby, trois méthodes d'implémentation de la synchronisation sont fournies, à savoir :
1Par l'implémentation de la classe Mutex, la synchronisation des threads est réalisée.
2. La classe Queue pour superviser la transmission des données implémente la synchronisation des threads
3. Utiliser ConditionVariable pour la synchronisation
La synchronisation des threads est réalisée à l'aide de la classe Mutex, si plusieurs threads ont besoin d'un programme variable en même temps, cette variable peut être verrouillée avec lock. Code suivant :
#!/usr/bin/ruby require "thread" puts "Synchroniser le Thread" @num=200 @mutex=Mutex.new def buyTicket(num) @mutex.lock if @num>=num @num=@num-num puts "Vous avez acheté avec succès #{num} billets" else puts "Désolé, pas assez de billets" end @mutex.unlock end billet1=Thread.new 10 do 10.times do |value| billetNum =15 buyTicket(billetNum) sleep 0.01 end end billet2=Thread.new 10 do 10.times do |value| billetNum =20 buyTicket(billetNum) sleep 0.01 end end sleep 1 billet1.join billet2.join
Le résultat de l'exécution du code suivant est :
Synchroniser le Thread Vous avez acheté avec succès 15 billets Vous avez acheté avec succès 20 billets Vous avez acheté avec succès 15 billets Vous avez acheté avec succès 20 billets Vous avez acheté avec succès 15 billets Vous avez acheté avec succès 20 billets Vous avez acheté avec succès 15 billets Vous avez acheté avec succès 20 billets Vous avez acheté avec succès 15 billets Vous avez acheté avec succès 20 billets Vous avez acheté avec succès 15 billets Désolé, pas assez de billets Désolé, pas assez de billets Désolé, pas assez de billets Désolé, pas assez de billets Désolé, pas assez de billets Désolé, pas assez de billets Désolé, pas assez de billets Désolé, pas assez de billets Désolé, pas assez de billets
En plus de verrouiller une variable avec un lock, on peut utiliser try_lock pour verrouiller une variable, ou utiliser Mutex.synchronize pour synchroniser l'accès à une variable.
La classe Queue représente une file qui supporte les threads, permettant une synchronisation d'accès à la fin de la file. Différents threads peuvent utiliser la même instance de la classe, mais sans se soucier de la synchronisation des données dans cette file. De plus, l'utilisation de la classe SizedQueue permet de limiter la longueur de la file
La classe SizedQueue peut grandement faciliter le développement d'applications synchronisées entre threads, car il n'est pas nécessaire de s'inquiéter de la synchronisation des threads une fois que l'élément est ajouté à cette file.
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) # Laisser le thread dormir pendant un certain temps queue << i puts "#{i} produced" end end consumer = Thread.new do 10.times do |i| value = queue.pop sleep rand(i/2) puts "consommé #{value}" end end consumer.join
Sortie du programme :
Test de SizedQuee 0 produit 1 produit consommé 0 2 produit consommé 1 consommé 2 3 produit consommé 34 produit consommé 4 5 produit consommé 5 6 produit consommé 6 7 produit consommé 7 8 produit 9 produit consommé 8 consommé 9
Les threads peuvent avoir des variables privées. Les variables privées des threads sont écrites au moment de la création du thread. Elles peuvent être utilisées à l'intérieur du domaine du thread, mais ne peuvent pas être partagées à l'extérieur du thread.
Mais que faire lorsque les variables locales d'un thread doivent être accédées par d'autres threads ou le thread principal ? Ruby permet de créer des variables de thread via des noms, semblables à considérer un thread comme une table de hachage. Vous pouvez écrire des données via []= et lire des données via []. Regardons le 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 l'exécution 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 soit exécuté, puis affiche chaque valeur séparément. 。
Le niveau de priorité des threads est l'un des principaux facteurs influençant le planificateur de threads. D'autres facteurs incluent la durée de l'exécution utilisant le CPU, la planification de groupement des threads, etc.
Vous pouvez obtenir le niveau de priorité d'un thread en utilisant la méthode Thread.priority et ajuster le niveau de priorité en utilisant Thread.priority=.
Le niveau de priorité par défaut des threads est 0. Les threads avec une priorité plus élevée s'exécutent plus rapidement.
Un thread peut accéder à toutes les données de son propre domaine, mais que faire s'il faut accéder aux données d'un autre thread dans un même thread ? La classe Thread fournit des méthodes pour l'accès mutuel des données entre les threads. Vous pouvez simplement considérer un thread comme un hash, où vous pouvez écrire des données à l'intérieur de n'importe quel thread en utilisant []= et lire des données en utilisant [].
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"]}"}
可以看到,把线程作为一个 Hash 表,使用 [] 和 []= 方法,我们实现了线程之间的数据共享。
Mutex(Mutual Exclusion = 互斥锁)是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。
#!/usr/bin/ruby require 'thread' count1 =2 = 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}"
以上示例运行输出结果为:
count1 : 9712487 count2 : 12501239 difference : 0
#!/usr/bin/ruby require 'thread' mutex = Mutex.new count1 =2 = 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}"
以上示例运行输出结果为:
count1 : 1336406 count2 : 1336406 difference : 0
两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出时,这种状况,就称为死锁。
例如,一个进程 p1占用了显示器,同时又必须使用打印机,而打印机被进程p2占用,p2又必须使用显示器,这样就形成了死锁。
当我们在使用 Mutex 对象时需要注意线程死锁。
#!/usr/bin/ruby require 'thread' mutex = Mutex.new cv = ConditionVariable.new a = Thread.new { mutex.synchronize { puts "A: J'ai la section critique, mais je vais attendre cv" cv.wait(mutex) puts "A: J'ai à nouveau la section critique ! Je règne !" } } puts "(Plus tard, de retour à la ferme...)" b = Thread.new { mutex.synchronize { puts "B: Maintenant, je suis dans la section critique, mais j'ai terminé avec cv" cv.signal puts "B: Je suis toujours dans la section critique, je termine" } } a.join b.join
Le résultat de la sortie de l'exemple ci-dessus est :
A: J'ai la section critique, mais je vais attendre cv (Plus tard, de retour à la ferme...) B: Maintenant, je suis dans la section critique, mais j'ai terminé avec cv B: Je suis toujours dans la section critique, je termine A: J'ai à nouveau la section critique ! Je règne !
Les méthodes de la classe Thread complète sont les suivantes :
Numéro | Description de la méthode |
---|---|
1 | Thread.abort_on_exception Si sa valeur est true, une fois qu'un thread s'arrête en raison d'une exception, l'interpréteur est interrompu. Sa valeur par défaut est false, ce qui signifie que dans les cas normaux, si un thread rencontre une exception et que cette exception n'est pas détectée par Thread#join, etc., le thread est terminé sans avertissement. |
2 | Thread.abort_on_exception= Si elle est définie comme trueUne fois qu'un thread s'arrête en raison d'une exception, l'interpréteur est interrompu. Retourne le nouveau statut |
3 | Thread.critical Retourner une valeur booléenne. |
4 | Thread.critical= Lorsque sa valeur est true, le basculement de thread ne se produit pas. Si le thread courant est en attente (stop) ou qu'il y a une intervention de signal, sa valeur devient automatiquement false. |
5 | Thread.current Retourne le thread en cours d'exécution (thread courant). |
6 | Thread.exit Termine l'exécution du thread courant. Retourne le thread courant. Si le thread courant est le seul thread, il utilisera exit(0) pour terminer son exécution. |
7 | Thread.fork { block } Crée un thread comme Thread.new. |
8 | Thread.kill(aThread) Termine l'exécution du thread. |
9 | Thread.list Retourne un tableau de threads actifs ou en attente. |
10 | Thread.main Retour au thread principal. |
11 | Thread.new([arg])* ) {| args | block } Créer un thread et commencer à l'exécuter. Les valeurs seront transmises en bloc à la méthode. Cela permet de transmettre des valeurs aux variables locales固有 du thread au moment de son lancement. |
12 | Thread.pass La main de l'exécution est passée à d'autres threads. Elle ne change pas l'état du thread en cours d'exécution, mais transfère le contrôle à d'autres threads exécutables (planification de thread explicite). |
13 | Thread.start( [ args ])* ) {| args | block } Créer un thread et commencer à l'exécuter. Les valeurs seront transmises en bloc à la méthode. Cela permet de transmettre des valeurs aux variables locales固有 du thread au moment de son lancement. |
14 | Thread.stop Suspendre le thread courant jusqu'à ce que d'autres threads utilisent la méthode run pour réveiller à nouveau ce thread. |
Voici un exemple qui appelle la méthode d'exemple de thread join :
#!/usr/bin/ruby thr = Thread.new do # Exemple puts "Dans le second thread" raise "Lève l'exception" end thr.join # Exemple de méthode d'exemple join
Voici une liste complète des méthodes d'exemple :
Numéro | Description de la méthode |
---|---|
1 | thr[ name ] Extraire les données固有 correspondant au nom dans le thread. name peut être une chaîne ou un symbole. Retourne nil si il n'y a pas de données correspondant au nom. |
2 | thr[ name ] = Définir la valeur des données固有 correspondant au nom dans le thread, name peut être une chaîne ou un symbole. Si nil est défini, les données correspondantes dans le thread seront supprimées. |
3 | thr.abort_on_exception Retourner une valeur booléenne. |
4 | thr.abort_on_exception= Si cette valeur est true, l'interpréteur entier sera interrompu une fois qu'un thread se termine par une exception. |
5 | thr.alive? Retourne true si le thread est 'vivant'. |
6 | thr.exit Terminer l'exécution du thread. Retourner self. |
7 | thr.join Suspendre le thread courant jusqu'à ce que le thread self termine son exécution. Si self termine par une exception, l'exception sera déclenchée sur le thread courant. |
8 | thr.key? Retourne true si les données固有 du thread correspondant au nom ont déjà été définies. |
9 | thr.kill Similaire à Thread.exit 。 |
10 | thr.priority Retourner la priorité du thread. La valeur par défaut de la priorité est 0. Plus cette valeur est grande, plus la priorité est élevée. |
11 | thr.priority= Définir la priorité du thread. Il peut également être définie sur un nombre négatif. |
12 | thr.raise( anException ) Lève une exception forceément dans ce thread. |
13 | thr.run Redémarrer le thread suspendu (stop). Contrairement à wakeup, il effectue immédiatement le basculement du thread. Si cette méthode est utilisée pour un processus mort, une exception ThreadError sera déclenchée. |
14 | thr.safe_level 返回self的安全等级。当前线程的safe_level与$SAFE相同。 |
15 | thr.status 使用字符串"run"、"sleep"或"aborting"来表示活线程的状态。如果某线程是正常终止的话,就返回false。如果因异常而终止的话,就返回nil。 |
16 | thr.stop? 如果线程处于终止状态(dead)或被挂起(stop)时,返回true。 |
17 | thr.value 等待self线程终止运行(等同于join)后,返回该线程的块的返回值。如果在线程运行过程中发生了异常,就会再次引发该异常。 |
18 | thr.wakeup 更改被挂起(stop)的线程的状态为可执行状态(run)。如果在执行该方法时对死线程进行操作,将会引发ThreadError异常。 |