Klasse Thread

Threads sind die Ruby-Implementierung für ein paralleles Programmiermodell.

Programme, die mehrere Ausführungs-Threads erfordern, sind ein perfekter Kandidat für Rubys Thread-Klasse.

Zum Beispiel können wir einen neuen Thread erstellen, der von der Ausführung des Haupt-Threads getrennt ist, indem wir ::new verwenden.

thr = Thread.new { puts "What's the big deal" }

Dann können wir die Ausführung des Haupt-Threads anhalten und unseren neuen Thread beenden lassen, indem wir join verwenden.

thr.join #=> "What's the big deal"

Wenn wir thr.join nicht aufrufen, bevor der Haupt-Thread beendet wird, werden alle anderen Threads, einschließlich thr, beendet.

Alternativ können Sie ein Array zur gleichzeitigen Verarbeitung mehrerer Threads verwenden, wie im folgenden Beispiel:

threads = []
threads << Thread.new { puts "What's the big deal" }
threads << Thread.new { 3.times { puts "Threads are fun!" } }

Nachdem einige Threads erstellt wurden, warten wir darauf, dass sie alle nacheinander beendet werden.

threads.each { |thr| thr.join }

Um den letzten Wert eines Threads abzurufen, verwenden Sie value.

thr = Thread.new { sleep 1; "Useful value" }
thr.value #=> "Useful value"

Thread-Initialisierung

Um neue Threads zu erstellen, bietet Ruby ::new, ::start und ::fork. Mit jeder dieser Methoden muss ein Block bereitgestellt werden, andernfalls wird ein ThreadError ausgelöst.

Beim Unterklassen der Thread-Klasse wird die initialize-Methode Ihrer Unterklasse von ::start und ::fork ignoriert. Andernfalls stellen Sie sicher, dass Sie super in Ihrer initialize-Methode aufrufen.

Thread-Beendigung

Für die Beendigung von Threads bietet Ruby eine Vielzahl von Möglichkeiten.

Die Klassenmethode ::kill dient dazu, einen gegebenen Thread zu beenden.

thr = Thread.new { sleep }
Thread.kill(thr) # sends exit() to thr

Alternativ können Sie die Instanzmethode exit oder eine ihrer Aliasnamen kill oder terminate verwenden.

thr.exit

Thread-Status

Ruby bietet einige Instanzmethoden zur Abfrage des Zustands eines gegebenen Threads. Um einen String mit dem aktuellen Zustand des Threads zu erhalten, verwenden Sie status.

thr = Thread.new { sleep }
thr.status # => "sleep"
thr.exit
thr.status # => false

Sie können auch alive? verwenden, um festzustellen, ob der Thread läuft oder schläft, und stop?, ob der Thread tot oder schläft.

Thread-Variablen und Gültigkeitsbereich

Da Threads mit Blöcken erstellt werden, gelten für die Gültigkeitsbereiche von Variablen die gleichen Regeln wie für andere Ruby-Blöcke. Alle lokalen Variablen, die innerhalb dieses Blocks erstellt werden, sind nur für diesen Thread zugänglich.

Fiber-lokal vs. Thread-lokal

Jede Fiber hat ihren eigenen Speicherbereich für Thread#[]-Speicherung. Wenn Sie eine neue Fiber-lokale Variable setzen, ist sie nur innerhalb dieser Fiber zugänglich. Zur Veranschaulichung:

Thread.new {
  Thread.current[:foo] = "bar"
  Fiber.new {
    p Thread.current[:foo] # => nil
  }.resume
}.join

Dieses Beispiel verwendet [] zum Abrufen und []= zum Setzen von Fiber-lokalen Variablen. Sie können auch keys verwenden, um die Fiber-lokalen Variablen eines bestimmten Threads aufzulisten, und key?, um zu prüfen, ob eine Fiber-lokale Variable existiert.

Was Thread-lokale Variablen betrifft, so sind sie im gesamten Gültigkeitsbereich des Threads zugänglich. Angenommen, das folgende Beispiel:

Thread.new{
  Thread.current.thread_variable_set(:foo, 1)
  p Thread.current.thread_variable_get(:foo) # => 1
  Fiber.new{
    Thread.current.thread_variable_set(:foo, 2)
    p Thread.current.thread_variable_get(:foo) # => 2
  }.resume
  p Thread.current.thread_variable_get(:foo)   # => 2
}.join

Sie sehen, dass die Thread-lokale Variable :foo in die Fiber übernommen wurde und am Ende des Threads auf 2 geändert wurde.

Dieses Beispiel verwendet thread_variable_set zum Erstellen neuer Thread-lokaler Variablen und thread_variable_get zum Referenzieren dieser.

Es gibt auch thread_variables zum Auflisten aller Thread-lokalen Variablen und thread_variable? zum Prüfen, ob eine gegebene Thread-lokale Variable existiert.

Exception-Behandlung

Wenn eine unbehandelte Ausnahme innerhalb eines Threads ausgelöst wird, beendet sich dieser. Standardmäßig wird diese Ausnahme nicht an andere Threads weitergegeben. Die Ausnahme wird gespeichert und wenn ein anderer Thread value oder join aufruft, wird die Ausnahme in diesem Thread erneut ausgelöst.

t = Thread.new{ raise 'something went wrong' }
t.value #=> RuntimeError: something went wrong

Eine Ausnahme kann von außerhalb des Threads mit der Instanzmethode Thread#raise ausgelöst werden, die dieselben Parameter wie Kernel#raise annimmt.

Das Setzen von Thread.abort_on_exception = true, Thread#abort_on_exception = true oder $DEBUG = true bewirkt, dass eine nachfolgende unbehandelte Ausnahme, die in einem Thread ausgelöst wird, automatisch im Haupt-Thread erneut ausgelöst wird.

Mit der Klassenmethode ::handle_interrupt können Sie jetzt Ausnahmen asynchron mit Threads behandeln.

Zeitplanung

Ruby bietet einige Möglichkeiten zur Unterstützung der Zeitplanung von Threads in Ihrem Programm.

Die erste Methode ist die Verwendung der Klassenmethode ::stop, um den aktuell laufenden Thread anzuhalten und die Ausführung eines anderen Threads zu planen.

Sobald ein Thread angehalten ist, können Sie die Instanzmethode wakeup verwenden, um Ihren Thread für die Zeitplanung zu kennzeichnen.

Sie können auch ::pass versuchen, der versucht, die Ausführung an einen anderen Thread zu übergeben, aber davon abhängig ist, ob das Betriebssystem einen laufenden Thread wechselt oder nicht. Dasselbe gilt für priority, mit dem Sie dem Thread-Scheduler Hinweise geben können, welche Threads bei der Übergabe der Ausführung Vorrang haben sollen. Diese Methode ist ebenfalls vom Betriebssystem abhängig und kann auf einigen Plattformen ignoriert werden.