Multithreading en Ruby
Chaque programme exécuté sur le système est un processus. Chaque processus contient un ou plusieurs threads.
Un thread est un flux de contrôle séquentiel unique dans un programme. L'exécution simultanée de plusieurs threads dans un même programme pour accomplir différentes tâches est appelée multithreading.
En Ruby, nous pouvons créer du multithreading via la classe Thread. Les threads Ruby sont légers et peuvent implémenter du code parallèle de manière efficace.
Création de threads Ruby
Pour démarrer un nouveau thread, appelez simplement Thread.new :
Exemple
L'exemple suivant montre comment utiliser le multithreading dans un programme Ruby :
Exemple
Essayez-le »
Le résultat d'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 des threads
1. Thread.new peut être utilisé pour créer un thread. De même, Thread.start ou Thread.fork peuvent être utilisés avec la même syntaxe pour créer des threads.
2. Une fois qu'un thread est créé, il n'est pas nécessaire de le démarrer ; le thread s'exécutera automatiquement.
3. La classe Thread définit certaines méthodes pour contrôler les threads. Un 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 les méthodes du thread. Si le thread a terminé son exécution, il renvoie la valeur du thread ; sinon, aucune valeur n'est renvoyée tant que le thread n'est pas terminé.
5. La méthode Thread.current renvoie l'objet représentant le thread actuel. La méthode Thread.main renvoie le thread principal.
6. Utilisez la méthode Thread.join pour exécuter des threads. Cette méthode suspend le thread principal jusqu'à ce que le thread actuel termine son exécution.
États des threads
Les threads ont 5 états :
| État du thread | Valeur de retour |
|---|---|
| Exécutable | run |
| En sommeil | En sommeil |
| En sortie | aborting |
| Terminaison normale | false |
| Terminé en raison d'une exception | nil |
Threads et exceptions
Lorsqu'une exception se produit dans un thread et n'est pas interceptée par rescue, le thread est généralement terminé sans avertissement. Toutefois, si d'autres threads attendent ce thread en raison de Thread#join, les threads en attente verront également la même exception levée.
En utilisant les 3 méthodes suivantes, vous pouvez faire en sorte que l'interpréteur interrompe l'exécution lorsqu'un thread se termine à cause d'une exception.
- Spécifiez lors du démarrage du script-dl'option, et exécutez en mode débogage.
- use
Thread.abort_on_exceptionDéfinissez l'indicateur. - Utiliser
Thread#abort_on_exceptionDéfinissez l'indicateur pour le thread spécifié.
Après avoir utilisé l'une des trois méthodes ci-dessus, l'interpréteur entier sera interrompu.
Contrôle de synchronisation des threads
En Ruby, il existe trois façons d'atteindre la synchronisation, à savoir :
1. Implémentation de la synchronisation des threads via la classe Mutex
2. Implémentation de la synchronisation des threads via la classe Queue pour le transfert de données
3. Utilisation de ConditionVariable pour le contrôle de synchronisation
Implémentation de la synchronisation des threads avec la classe Mutex
Pour implémenter un contrôle de synchronisation des threads via la classe Mutex, si plusieurs threads ont besoin d'une variable de programme en même temps, vous pouvez verrouiller cette variable à l'aide de lock. Le code est le suivant :
Exemple
Essayez-le »
Le résultat d'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 lock pour verrouiller une variable, vous pouvez également utiliser try_lock pour verrouiller une variable, et vous pouvez utiliser Mutex.synchronize pour synchroniser l'accès à une variable.
Mise en œuvre de la synchronisation des threads avec la classe Queue pour le transfert de données
La classe Queue représente une file d'attente sûre pour les threads qui peut synchroniser l'accès à la fin de la file. Différents threads peuvent utiliser la même classe de file sans se soucier de savoir si les données de la file peuvent être synchronisées. De plus, l'utilisation de la classe SizedQueue peut limiter la longueur de la file.
La classe SizedQueue peut nous aider très facilement à développer des applications synchronisées entre threads, car tant que des éléments sont ajoutés à cette file, il n'est pas nécessaire de se soucier des problèmes de synchronisation des threads.
Le problème classique du producteur-consommateur :
Exemple
Essayez-le »
La 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
Variables de thread
Les threads peuvent avoir leurs propres variables privées. Les variables privées d'un thread sont écrites dans le thread lors de sa création. Elles peuvent être utilisées dans la portée du thread, mais ne peuvent pas être partagées en dehors du thread.
Mais parfois, que se passe-t-il si les variables locales d'un thread doivent être accessibles par d'autres threads ou le thread principal ? Ruby fournit un moyen de créer des variables de thread par nom, en traitant un thread comme une table de correspondance de style hash. Utilisez []= pour écrire des données et [] pour lire des données. Regardons le code suivant :
Exemple
La sortie du code ci-dessus est :
8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10
Le thread principal attend que les threads enfants terminent leur exécution, puis affiche chaque valeur séparément.
Priorité des threads
La priorité d'un thread est le principal facteur influençant l'ordonnancement des threads. D'autres facteurs incluent la durée d'exécution CPU, l'ordonnancement des groupes de threads, etc.
Vous pouvez utiliser la méthode Thread.priority pour obtenir la priorité d'un thread et utiliser la méthode Thread.priority= pour ajuster la priorité du thread.
La priorité de thread par défaut est 0. Les threads de priorité plus élevée s'exécutent plus rapidement.
Un Thread peut accéder à toutes les données de sa propre portée, mais que se passe-t-il si vous devez accéder aux données d'autres threads depuis un certain thread ? La classe Thread fournit des méthodes permettant aux threads d'accéder aux données les uns des autres. Vous pouvez simplement traiter un thread comme une table de hachage, et utiliser []= dans n'importe quel thread pour écrire des données, et [] pour lire des données.
On peut voir qu'en traitant un thread comme une table de hachage et en utilisant les méthodes [] et []=, nous avons réalisé le partage de données entre threads.
Exclusion mutuelle des threads
Mutex (Exclusion mutuelle = verrou mutex) est un mécanisme utilisé en programmation multithread pour empêcher deux threads de lire et écrire simultanément la même ressource partagée (comme une variable globale).
Exemple sans utiliser Mutex
Exemple
La sortie de l'exemple ci-dessus est :
count1 : 9712487 count2 : 12501239 difference : 0
Exemple d'utilisation de mutex
Exemple
La sortie de l'exemple ci-dessus est :
count1 : 1336406 count2 : 1336406 difference : 0
Interblocage
Lorsque deux ou plusieurs unités de calcul attendent chacune que l'autre cesse de s'exécuter afin d'obtenir des ressources système, mais qu'aucune ne se termine à l'avance, cette situation est appelée interblocage.
Par exemple, si le processus p1 occupe le moniteur et doit aussi utiliser l'imprimante, mais que l'imprimante est occupée par le processus p2, et que p2 doit utiliser le moniteur, cela crée un interblocage.
Lors de l'utilisation d'objets Mutex, nous devons être attentifs aux interblocages de threads.
Exemple
La 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éthodes de la classe Thread
Les méthodes complètes de la classe Thread sont les suivantes :
| Numéro de série | Description de la méthode |
|---|---|
| 1 | Thread.abort_on_exception Si sa valeur est true, dès qu'un thread se termine en raison d'une exception, l'interpréteur entier sera interrompu. Sa valeur par défaut est false. Autrement dit, dans des circonstances normales, si un thread lève une exception qui n'est pas détectée par Thread#join ou similaire, le thread sera terminé sans avertissement. |
| 2 | Thread.abort_on_exception= S'il est défini surtrue, dès qu'un thread se termine en raison d'une exception, l'interpréteur entier sera interrompu. Renvoie le nouvel état. |
| 3 | Thread.critical Renvoie une valeur booléenne. |
| 4 | Thread.critical= Lorsque sa valeur est true, la commutation de threads ne se produira pas. Si le thread courant est suspendu (stop) ou est interrompu par un signal, sa valeur devient automatiquement false. |
| 5 | Thread.current Retourne le thread en cours d'exécution (thread courant). |
| 6 | Thread.exit Met fin à 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 exactement comme Thread.new. |
| 8 | Thread.kill( aThread ) Met fin à l'exécution du thread. |
| 9 | Thread.list Retourne un tableau de threads vivants qui sont dans l'état en cours d'exécution ou suspendu. |
| 10 | Thread.main Retourne le thread principal. |
| 11 | Thread.new( [ arg ]* ) {| args | block } Crée un thread et démarre l'exécution. Les arguments sont passés au bloc tels quels. Cela permet de passer des valeurs aux variables locales du thread lors du démarrage de celui-ci. |
| 12 | Thread.pass Cède le passage à d'autres threads. Cela ne change pas l'état du thread en cours d'exécution, mais transfère le contrôle à d'autres threads exécutables (ordonnancement explicite des threads). |
| 13 | Thread.start( [ args ]* ) { | args | block } Crée un thread et démarre l'exécution. Les arguments sont passés au bloc tels quels. Cela permet de passer des valeurs aux variables locales du thread lors du démarrage de celui-ci. |
| 14 | Thread Suspend le thread courant jusqu'à ce qu'un autre thread utilise la méthode run pour le réveiller à nouveau |
Méthodes d'instance de Thread
L'exemple suivant appelle la méthode d'instanciation de thread join :
Exemple
Voici la liste complète des méthodes d'instanciation :
| Non. | Description de la méthode |
|---|---|
| 1 | thr[ name ] Récupère les données locales au thread correspondant à name. name peut être une chaîne de caractères ou un symbole. S'il n'existe pas de données correspondant à name, nil est retourné. |
| 2 | thr[ name ] = Définit la valeur des données locales au thread correspondant à name. name peut être une chaîne de caractères ou un symbole. Si défini à nil, les données correspondantes dans le thread sont supprimées. |
| 3 | thr.abort_on_exception Renvoie une valeur booléenne. |
| 4 | thr.abort_on_exception= Si sa valeur est true, dès qu'un thread se termine en raison d'une exception, l'interpréteur entier est interrompu. |
| 5 | thr.alive? Retourne true si le thread est vivant. |
| 6 | thr.exit Termine l'exécution du thread. Retourne self. |
| 7 | thr.join Suspend le thread courant jusqu'à ce que le thread self se termine. Si self se termine à cause d'une exception, le thread courant lèvera la même exception. |
| 8 | thr.key? Renvoie true si les données locales au thread correspondant à name ont déjà été définies. |
| 9 | thr.kill Similaire àThread.exit 。 |
| 10 | thr.priority Renvoie la priorité du thread. La priorité par défaut est 0. Plus la valeur est grande, plus la priorité est élevée. |
| 11 | thr.priority= Définit la priorité du thread. Elle peut également être définie sur un nombre négatif. |
| 12 | thr.raise( anException ) Lève de force une exception dans le thread. |
| 13 | thr.run Redémarre un thread suspendu (stop). Contrairement à wakeup, il effectue immédiatement un changement de thread. Si cette méthode est utilisée sur un thread mort, une exception ThreadError est levée. |
| 14 | thr.safe_level Renvoie le niveau de sécurité de self. Le safe_level du thread courant est identique à $SAFE. |
| 15 | thr.status Représente l'état d'un thread vivant à l'aide des chaînes "run", "sleep" ou "aborting". Renvoie false si un thread s'est terminé normalement. Renvoie nil s'il s'est terminé en raison d'une exception. |
| 16 | thr.stop? Renvoie true si le thread est mort ou suspendu (stop). |
| 17 | thr.value Attend que le thread self se termine (équivalent à join), puis renvoie la valeur de retour du bloc du thread. Si une exception s'est produite pendant l'exécution du thread, cette exception est à nouveau levée. |
| 18 | thr.wakeup Change l'état d'un thread suspendu (stop) en exécutable (run). Si cette méthode est exécutée sur un thread mort, une exception ThreadError est levée. |