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:

#Thread #1 Codeabschnitt Thread.new { #Thread #2 führt Code aus } #Thread #1 führt Code aus

Beispiel

Das folgende Beispiel zeigt, wie Multithreading in einem Ruby-Programm verwendet wird:

Beispiel

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

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-StatusRückgabewert
Ausführbarrun
SchlafendSchlafend
Beendendabbrechend
Normale Beendigungfalse
Wegen einer Ausnahme beendetnil

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.

begin t = Thread.new do Thread.pass #Der Hauptthread wartet tatsächlich auf join raise "unbehandelte Ausnahme" end t.join rescue p $! # => "unbehandelte Ausnahme" end

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 SieThread.abort_on_exceptionSetzen Sie das Flag.
  • Verwenden SieThread#abort_on_exceptionSetzen Sie das Flag für den angegebenen Thread.

Nach Verwendung einer der oben genannten drei Methoden wird der gesamte Interpreter unterbrochen.

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

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

#!/usr/bin/ruby require "thread" puts "Thread synchronisieren" @num=200 @mutex=Mutex.new def buyTicket(num) @mutex.lock if @num>=num @num=@num-num puts "Sie haben erfolgreich #{num} Tickets gekauft" else puts "Entschuldigung, nicht genügend 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

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

#!/usr/bin/ruby require "thread" puts "SizedQuee Test" queue = Queue.new producer = Thread.new do 10.times do |i| sleep rand(i) #Lass den Thread eine Weile schlafen 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

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

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

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.

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

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

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

Die Ausgabe des obigen Beispiels ist:

count1 :  9712487
count2 :  12501239
difference : 0

Beispiel für die Verwendung von Mutex

Beispiel

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

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

#!/usr/bin/ruby require 'thread' mutex = Mutex.new cv = ConditionVariable.new a = Thread.new { mutex.synchronize { puts "A: Ich habe den kritischen Abschnitt, werde aber auf cv warten" cv.wait(mutex) puts "A: Ich habe wieder den kritischen Abschnitt! Ich habe das Sagen!" } } puts "(Später, zurück auf der Ranch...)" b = Thread.new { mutex.synchronize { puts "B: Jetzt bin ich im kritischen Abschnitt, aber mit cv fertig" cv.signal puts "B: Ich bin immer noch im kritischen Abschnitt und schließe gerade ab." } } a.join b.join

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 NummerMethodenbeschreibung
1Thread.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.
2Thread.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.
3Thread.critical
Gibt einen booleschen Wert zurück.
4Thread.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.
5Thread.current
Gibt den aktuell laufenden Thread (aktueller Thread) zurück.
6Thread.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.
7Thread.fork { block }
Erstellt einen Thread genauso wie Thread.new.
8Thread.kill( aThread )
Beendet die Ausführung des Threads.
9Thread.list
Gibt ein Array lebender Threads zurück, die sich im laufenden oder angehaltenen Zustand befinden.
10Thread.main
Gibt den Hauptthread zurück.
11Thread.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.
12Thread.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).
13Thread.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.
14Thread.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

#!/usr/bin/ruby thr = Thread.new do #Instanziierung puts "Im zweiten Thread" raise "Ausnahme auslösen" end thr.join #Ruft die Instanziierungsmethode join auf

Das Folgende ist die vollständige Liste der Instanziierungsmethoden:

Nein.Methodenbeschreibung
1thr[ 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.
2thr[ 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.
3thr.abort_on_exception
Gibt einen booleschen Wert zurück.
4thr.abort_on_exception=
Wenn sein Wert true ist, wird der gesamte Interpreter unterbrochen, sobald ein Thread aufgrund einer Ausnahme beendet wird.
5thr.alive?
Gibt true zurück, wenn der Thread lebendig ist.
6thr.exit
Beendet die Ausführung des Threads. Gibt self zurück.
7thr.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.
8thr.key?
Gibt true zurück, wenn die zu name gehörenden thread-lokalen Daten bereits definiert wurden.
9thr.kill
Ähnlich wieThread.exit 。
10thr.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.
11thr.priority=
Legt die Priorität des Threads fest. Sie kann auch auf eine negative Zahl gesetzt werden.
12thr.raise( anException )
Löst zwangsweise eine Ausnahme innerhalb des Threads aus.
13thr.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.
14thr.safe_level
Gibt die Sicherheitsstufe von self zurück. Der safe_level des aktuellen Threads ist derselbe wie $SAFE.
15thr.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.
16thr.stop?
Gibt true zurück, wenn der Thread tot oder angehalten (stop) ist.
17thr.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.
18thr.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.
Weitere Erweiterungen