Ruby-Programmierung: Threads

Aus Wikibooks

Zurück zum Inhaltsverzeichnis.

Mehrkernprozessoren bieten die Möglichkeit eine höhere Geschwindigkeit der Programme zu erreichen, da die Berechnungen parallel ausgeführt werden können. Diese Form der Parallelität bezeichnet man als echt, da die Berechnungen wirklich zur gleichen Zeit ausgeführt werden. So könnte man sich ein Programm vorstellen, dass auf einem großen Array eine parallel_each ausführt. Dies wird von MRI nicht unterstüzt. In anderen Implementationen wie JRuby und Rubinius ist es dagegen möglich mehrere Prozessoren zu nutzen.

Trotzdem ist es sinnvoll sich auch bei der Verwendung von MRI über die Benutzung von Threads Gedanken zu machen, nämlich dann, wenn es um virtuelle Parallelität handelt. Dabei werden zwar keine Operationen zeitlich nebeneinander ausgeführt, jedoch erfolgt die Berechnung und der Wechsel so schnell, dass es für den Nutzer wie eine parallele Ausführung aussieht. Zu Geschwindigkeitsvorteilen kann es dann kommen, wenn der eine genutzte Prozessor ansonsten warten würde, zum Beispiel auf Daten, die über eine Netzwerkverbindung übertragen werden.

Threads[Bearbeiten]

Ein neuer Thread ist ein Objekt der Klasse Thread. Beim initialisieren erhält es einen Codeblock, den der neue Thread ausführt. Für die Benutzung von Variablen innerhalb des Threads ergeben sich dieselben Sichtbarkeitsregeln wie für Blöcke im Allgemeinen, daher kann der Thread auf alle Variablen zugreifen, die er benutzt oder vor Aufrufen des Threads sichtbar waren.

i = 0
Thread.new do
  loop do
    i += 1
  end
end

gets
puts "Der Thread hat bis #{ i } gezaehlt!"

Dieses einfache Beispiel erzeugt eine Variable i und inkrementiert diese in einem neuen Thread in einer Endlosschleife. Am Ende wird die Zählung ausgegeben. Ein Thread wird entweder beendet, wenn das Ende des Codeblocks erreicht ist, oder wenn das Hauptprogramm sich beendet. Auf das Ende eines Threads kann man mit der Methode join warten.

i = 0
t = Thread.new do
  while i < 100000
    i += 1
  end
end

t.join
puts i

Wenn Sie die Zeile t.join auskommentieren, werden Sie feststellen, dass dem Skript nichtmehr die Zeit bleibt um die Zählung zu beenden.

Prozesse unter Unix mit fork[Bearbeiten]

Neben Threads exisitiert auch das Konzept von Prozessen. In beiden Fällen wird Code parallel ausgeführt, allerdings besteht ein großer Unterschied was die mögliche Kommunikation der Prozesse betrifft. Zwei Threads eines Programms teilen sich einen gemeinsamen Hauptspeicherbereich, dadurch ist es möglich Daten zwischen den Threads einfach auszutauschen über Variablen, die vor dem aufrufen des Threads sichtbar waren. Prozesse laufen in unterschiedlichen Speicherbreichen und können daher nicht direkt miteinander kommunizieren. Das Betriebssystem bietet an dieser Stelle andere Möglichkeiten Daten zwischen den Prozessen auszutauschen.

Die Methode fork verhält sich ähnlich wie Thread.new.

puts "Before fork"
pid = fork { puts "Inside fork" }
puts "pid: #{ pid }"

Um den fork auszuführen wird der gesamte Prgrammstatus, wie zum Beispiel der Inhalt aller Variablen kopiert und erst danach das neue Programm ausgeführt. Dadurch ist es zwar möglich auf den Zustand des Hauptprogramms zuzugreifen, nicht jedoch diesen zu verändern. Ein Hauptunterschied zu Threads ist, das selbst beim Beenden des Hauptprogramms alle Forks weiter laufen und sich selber Beenden müssen.

Diese Methode kann tatsächlich auf mehreren Prozessorkernen parallel laufen, da die komplette Rubyimplementierung mitgeforkt wird, was auch deren Nachteil gegenüber Threads leicht erkennbar macht. Forks sind teuer und lohnen sich nur bei langlaufenden Anteilen im Programm, da ansonsten der Aufwand das Programm zu kopieren den Nutzen der Gleichzeitigkeit aufhebt. Threads dagegen sind recht leichtgewichtig erzeugen jedoch keine echte Parallelität.