Ruby-Multithreading
Jedes auf dem System laufende Programm ist ein Prozess. Jeder Prozess enthält einen oder mehrere Threads.
Ein Thread ist ein einzelner sequenzieller Kontrollfluss in einem Programm. Das gleichzeitige Ausführen mehrerer Threads in einem einzigen Programm, um verschiedene Aufgaben zu erledigen, wird als Multithreading bezeichnet.
In Ruby können wir Multithreading über die Klasse Thread erstellen. Ruby-Threads sind leichtgewichtig und können parallelen Code auf effiziente Weise implementieren.
Ruby-Threads erstellen
Um einen neuen Thread zu starten, rufen Sie einfach Thread.new auf:
Beispiel
Das folgende Beispiel zeigt, wie Multithreading in einem Ruby-Programm verwendet wird:
Beispiel
Ausprobieren »
Das Ausführungsergebnis des obigen Codes ist:
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
Thread-Lebenszyklus
1. Thread.new kann verwendet werden, um einen Thread zu erstellen. Ähnlich können Thread.start oder Thread.fork mit derselben Syntax verwendet werden, um Threads zu erstellen.
2. Nachdem ein Thread erstellt wurde, muss er nicht gestartet werden; der Thread wird automatisch ausgeführt.
3. Die Klasse Thread definiert einige Methoden zur Steuerung von Threads. Ein Thread führt den Codeblock in Thread.new aus.
4. Die letzte Anweisung im Codeblock des Threads ist der Wert des Threads, der über Thread-Methoden abgerufen werden kann. Wenn der Thread die Ausführung beendet hat, gibt er den Thread-Wert zurück; andernfalls wird kein Wert zurückgegeben, bis der Thread abgeschlossen ist.
5. Die Methode Thread.current gibt das Objekt zurück, das den aktuellen Thread repräsentiert. Die Methode Thread.main gibt den Haupt-Thread zurück.
6. Verwenden Sie die Methode Thread.join, um Threads auszuführen. Diese Methode suspendiert den Haupt-Thread, bis der aktuelle Thread die Ausführung beendet hat.
Thread-Zustände
Threads haben 5 Zustände:
| Thread-Status | Rückgabewert |
|---|---|
| Ausführbar | run |
| Schlafend | Schlafend |
| Beendend | abbrechend |
| Normale Beendigung | false |
| Wegen einer Ausnahme beendet | nil |
Threads und Ausnahmen
Wenn in einem Thread eine Ausnahme auftritt und nicht von rescue abgefangen wird, wird der Thread normalerweise ohne Warnung beendet. Wenn jedoch andere Threads aufgrund von Thread#join auf diesen Thread warten, wird in den wartenden Threads ebenfalls dieselbe Ausnahme ausgelöst.
Mit den folgenden 3 Methoden können Sie den Interpreter dazu bringen, die Ausführung zu unterbrechen, wenn ein Thread aufgrund einer Ausnahme beendet wird.
- Geben Sie beim Starten des Skripts die-dOption an und führen Sie es im Debug-Modus aus.
- Verwenden Sie
Thread.abort_on_exceptionSetzen Sie das Flag. - Verwenden Sie
Thread#abort_on_exceptionSetzen Sie das Flag für den angegebenen Thread.
Nach Verwendung einer der oben genannten drei Methoden wird der gesamte Interpreter unterbrochen.
Thread-Synchronisationssteuerung
In Ruby gibt es drei Möglichkeiten, Synchronisation zu erreichen, nämlich:
1. Implementierung der Thread-Synchronisation durch die Mutex-Klasse
2. Implementierung der Thread-Synchronisation durch die Queue-Klasse zur Datenübergabe
3. Verwendung von ConditionVariable zur Synchronisationssteuerung
Thread-Synchronisation mit der Mutex-Klasse implementieren
Um die Thread-Synchronisationssteuerung über die Mutex-Klasse zu implementieren: Wenn mehrere Threads gleichzeitig eine Programmvariable benötigen, können Sie diese Variable mit lock sperren. Der Code sieht wie folgt aus:
Beispiel
Ausprobieren »
Das Ausführungsergebnis des obigen Codes ist:
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
Zusätzlich zur Verwendung von lock zum Sperren einer Variable können Sie auch try_lock verwenden, um eine Variable zu sperren, und Sie können Mutex.synchronize verwenden, um den Zugriff auf eine Variable zu synchronisieren.
Implementierung der Thread-Synchronisierung mit der Queue-Klasse für die Datenübergabe
Die Queue-Klasse stellt eine threadsichere Warteschlange dar, die den Zugriff auf das Ende der Warteschlange synchronisieren kann. Verschiedene Threads können dieselbe Queue-Klasse verwenden, ohne sich darum kümmern zu müssen, ob die Daten in der Warteschlange synchronisiert werden können. Darüber hinaus kann die Verwendung der Klasse SizedQueue die Länge der Warteschlange begrenzen.
Die Klasse SizedQueue kann uns sehr bequem dabei helfen, thread-synchronisierte Anwendungen zu entwickeln, denn solange Elemente zu dieser Warteschlange hinzugefügt werden, muss man sich keine Gedanken über Thread-Synchronisierungsprobleme machen.
Das klassische Erzeuger-Verbraucher-Problem:
Beispiel
Ausprobieren »
Die Ausgabe des Programms:
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
Thread-Variablen
Threads können ihre eigenen privaten Variablen haben. Die privaten Variablen eines Threads werden in den Thread geschrieben, wenn er erstellt wird. Sie können innerhalb des Gültigkeitsbereichs des Threads verwendet werden, aber nicht außerhalb des Threads gemeinsam genutzt werden.
Aber manchmal, was ist, wenn auf die lokalen Variablen eines Threads von anderen Threads oder vom Hauptthread aus zugegriffen werden muss? Ruby bietet eine Möglichkeit, Thread-Variablen nach Namen zu erstellen, indem ein Thread wie eine Hash-artige Nachschlagetabelle behandelt wird. Verwenden Sie []=, um Daten zu schreiben, und [], um Daten zu lesen. Sehen wir uns den folgenden Code an:
Beispiel
Die Ausgabe des obigen Codes ist:
8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10
Der Hauptthread wartet darauf, dass die untergeordneten Threads die Ausführung beenden, und gibt dann jeden Wert separat aus.
Thread-Priorität
Die Thread-Priorität ist der Hauptfaktor, der die Thread-Planung beeinflusst. Weitere Faktoren sind die Länge der CPU-Ausführungszeit, die Thread-Gruppen-Planung und so weiter.
Sie können die Methode Thread.priority verwenden, um die Priorität eines Threads abzurufen, und die Methode Thread.priority= verwenden, um die Priorität des Threads anzupassen.
Die Standard-Thread-Priorität ist 0. Threads mit höherer Priorität werden schneller ausgeführt.
Ein Thread kann auf alle Daten in seinem eigenen Bereich zugreifen, aber was ist, wenn Sie aus einem bestimmten Thread heraus auf die Daten anderer Threads zugreifen müssen? Die Klasse Thread stellt Methoden bereit, mit denen Threads auf die Daten der jeweils anderen zugreifen können. Sie können einen Thread einfach wie eine Hash-Tabelle behandeln und []= in jedem Thread verwenden, um Daten zu schreiben, und [], um Daten zu lesen.
Man kann sehen, dass wir durch die Behandlung eines Threads als Hash-Tabelle und die Verwendung der Methoden [] und []= eine gemeinsame Nutzung von Daten zwischen Threads erreicht haben.
Gegenseitiger Ausschluss von Threads
Mutex (Mutual Exclusion = Mutex-Sperre) ist ein Mechanismus, der in der Multithread-Programmierung verwendet wird, um zu verhindern, dass zwei Threads gleichzeitig dieselbe gemeinsam genutzte Ressource (z. B. eine globale Variable) lesen und schreiben.
Beispiel ohne Verwendung von Mutex
Beispiel
Die Ausgabe des obigen Beispiels ist:
count1 : 9712487 count2 : 12501239 difference : 0
Beispiel für die Verwendung von Mutex
Beispiel
Die Ausgabe des obigen Beispiels ist:
count1 : 1336406 count2 : 1336406 difference : 0
Deadlock
Wenn zwei oder mehr Recheneinheiten jeweils darauf warten, dass die anderen aufhören zu laufen, um Systemressourcen zu erhalten, aber keine vorzeitig beendet wird, wird diese Situation als Deadlock bezeichnet.
Zum Beispiel: Wenn Prozess p1 den Monitor belegt und außerdem den Drucker verwenden muss, der Drucker aber von Prozess p2 belegt wird und p2 den Monitor verwenden muss, entsteht ein Deadlock.
Bei der Verwendung von Mutex-Objekten müssen wir auf Thread-Deadlocks achten.
Beispiel
Die Ausgabe des obigen Beispiels ist:
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!
Thread-Klassenmethoden
Die vollständigen Methoden der Thread-Klasse sind wie folgt:
| Laufende Nummer | Methodenbeschreibung |
|---|---|
| 1 | Thread.abort_on_exception Wenn sein Wert true ist, wird, sobald ein Thread aufgrund einer Ausnahme beendet wird, der gesamte Interpreter unterbrochen. Sein Standardwert ist false. Das heißt, unter normalen Umständen wird, wenn ein Thread eine Ausnahme auslöst, die nicht von Thread#join oder Ähnlichem erkannt wird, der Thread ohne Warnung beendet. |
| 2 | Thread.abort_on_exception= Wenn auftruegesetzt wird, wird, sobald ein Thread aufgrund einer Ausnahme beendet wird, der gesamte Interpreter unterbrochen. Gibt den neuen Zustand zurück. |
| 3 | Thread.critical Gibt einen booleschen Wert zurück. |
| 4 | Thread.critical= Wenn sein Wert true ist, erfolgt kein Thread-Wechsel. Wenn der aktuelle Thread angehalten (stop) oder durch ein Signal unterbrochen wird, wird sein Wert automatisch false. |
| 5 | Thread.current Gibt den aktuell laufenden Thread (aktueller Thread) zurück. |
| 6 | Thread.exit Beendet die Ausführung des aktuellen Threads. Gibt den aktuellen Thread zurück. Wenn der aktuelle Thread der einzige Thread ist, verwendet er exit(0), um seine Ausführung zu beenden. |
| 7 | Thread.fork { block } Erstellt einen Thread genauso wie Thread.new. |
| 8 | Thread.kill( aThread ) Beendet die Ausführung des Threads. |
| 9 | Thread.list Gibt ein Array lebender Threads zurück, die sich im laufenden oder angehaltenen Zustand befinden. |
| 10 | Thread.main Gibt den Hauptthread zurück. |
| 11 | Thread.new( [ arg ]* ) {| args | block } Erzeugt einen Thread und startet die Ausführung. Die Argumente werden unverändert an den Block übergeben. Dadurch ist es möglich, beim Starten des Threads Werte an die lokalen Variablen des Threads zu übergeben. |
| 12 | Thread.pass Übergibt die Ausführung an andere Threads. Es ändert nicht den Zustand des laufenden Threads, sondern übergibt die Kontrolle an andere lauffähige Threads (explizite Thread-Planung). |
| 13 | Thread.start( [ args ]* ) {| args | block } Erzeugt einen Thread und startet die Ausführung. Die Argumente werden unverändert an den Block übergeben. Dadurch ist es möglich, beim Starten des Threads Werte an die lokalen Variablen des Threads zu übergeben. |
| 14 | Thread.stop Hält den aktuellen Thread an, bis ein anderer Thread die run-Methode verwendet, um ihn wieder aufzuwecken. |
Thread-Instanzmethoden
Das folgende Beispiel ruft die Thread-Instanziierungsmethode join auf:
Beispiel
Das Folgende ist die vollständige Liste der Instanziierungsmethoden:
| Nein. | Methodenbeschreibung |
|---|---|
| 1 | thr[ name ] Ruft die thread-lokalen Daten ab, die name entsprechen. name kann ein String oder ein Symbol sein. Wenn keine Daten zu name vorhanden sind, wird nil zurückgegeben. |
| 2 | thr[ name ] = Setzt den Wert der thread-lokalen Daten, die name entsprechen. name kann ein String oder ein Symbol sein. Wenn auf nil gesetzt, werden die entsprechenden Daten im Thread gelöscht. |
| 3 | thr.abort_on_exception Gibt einen booleschen Wert zurück. |
| 4 | thr.abort_on_exception= Wenn sein Wert true ist, wird der gesamte Interpreter unterbrochen, sobald ein Thread aufgrund einer Ausnahme beendet wird. |
| 5 | thr.alive? Gibt true zurück, wenn der Thread lebendig ist. |
| 6 | thr.exit Beendet die Ausführung des Threads. Gibt self zurück. |
| 7 | thr.join Hält den aktuellen Thread an, bis der self-Thread beendet wird. Wenn self aufgrund einer Ausnahme beendet wird, löst der aktuelle Thread dieselbe Ausnahme aus. |
| 8 | thr.key? Gibt true zurück, wenn die zu name gehörenden thread-lokalen Daten bereits definiert wurden. |
| 9 | thr.kill Ähnlich wieThread.exit 。 |
| 10 | thr.priority Gibt die Priorität des Threads zurück. Die Standardpriorität ist 0. Je größer der Wert, desto höher die Priorität. |
| 11 | thr.priority= Legt die Priorität des Threads fest. Sie kann auch auf eine negative Zahl gesetzt werden. |
| 12 | thr.raise( anException ) Löst zwangsweise eine Ausnahme innerhalb des Threads aus. |
| 13 | thr.run Startet einen angehaltenen (stop) Thread neu. Im Gegensatz zu wakeup führt es sofort einen Thread-Wechsel durch. Wenn diese Methode auf einen toten Thread angewendet wird, wird eine ThreadError-Ausnahme ausgelöst. |
| 14 | thr.safe_level Gibt die Sicherheitsstufe von self zurück. Der safe_level des aktuellen Threads ist derselbe wie $SAFE. |
| 15 | thr.status Stellt den Status eines lebenden Threads unter Verwendung der Zeichenfolgen "run", "sleep" oder "aborting" dar. Gibt false zurück, wenn ein Thread normal beendet wurde. Gibt nil zurück, wenn er aufgrund einer Ausnahme beendet wurde. |
| 16 | thr.stop? Gibt true zurück, wenn der Thread tot oder angehalten (stop) ist. |
| 17 | thr.value Wartet, bis der Thread self beendet ist (entspricht join), und gibt dann den Rückgabewert des Blocks des Threads zurück. Wenn während der Ausführung des Threads eine Ausnahme aufgetreten ist, wird diese Ausnahme erneut ausgelöst. |
| 18 | thr.wakeup Ändert den Zustand eines angehaltenen (stop) Threads in ausführbar (run). Wenn diese Methode auf einem toten Thread ausgeführt wird, wird eine ThreadError-Ausnahme ausgelöst. |