Die Programmiersprache Ruby (German Edition)
erzeugt. Threads können daher in einer Baumstruktur angeordnet werden, wobei jeder Thread einen Eltern-Thread und eine Reihe von Kind-Threads haben kann. Die Klasse
Thread
verwaltet diese Informationen allerdings nicht – Threads werden im Allgemeinen als autonom und nicht als dem Thread untergeordnet betrachtet, der ihn erzeugt hat.
Wenn Sie eine Untermenge von Threads irgendwie ordnen wollen, können Sie ein
ThreadGroup
-Objekt erzeugen und ihm Threads hinzufügen:
group = ThreadGroup.new
3.times {|n| group.add(Thread.new { do_task(n) }}
Neue Threads werden anfangs in der Gruppe untergebracht, zu der ihr Eltern-Thread gehört. Mit der Instanzmethode
group
fragen Sie die
ThreadGroup
ab, zu der ein Thread gehört. Und mit der Methode
list
von
ThreadGroup
erhalten Sie ein Array der Threads in einer Gruppe. Wie die Klassenmethode
Thread.list
liefert die Instanzmethode
ThreadGroup.list
nur Threads zurück, die noch nicht beendet sind. Sie können mit der Methode
list
Methoden definieren, die mit allen Threads einer Gruppe arbeiten. So eine Methode kann zum Beispiel die Priorität aller Threads in der Gruppe reduzieren.
Was die Klasse
ThreadGroup
gegenüber einem einfachen Array mit Threads vorteilhaft erscheinen lässt, ist ihre Methode
enclose
. Nachdem eine Thread-Gruppe geschlossen worden ist, können keine Threads mehr aus ihr entfernt und keine neuen hinzugefügt werden. Die Threads in der Gruppe können neue Threads erzeugen, und diese neuen Threads werden auch zu Elementen der Gruppe. Eine geschlossene
ThreadGroup
ist nützlich, wenn Sie bestimmtem Ruby-Code nicht trauen und ihn unter der Variablen
$SAFE
ausführen (siehe „10.5 Sicherheit“ ). Damit können Sie beobachten, was für Threads von diesem Code erzeugt werden.
9.9.6 Threading-Beispiele
Nachdem wir nun das Thread-Modell und die Thread-API von Ruby erklärt haben, werden wir uns ein paar Beispiele für Multithread-Code anschauen.
9.9.6.1 Dateien parallel lesen
Threads werden in Ruby am häufigsten in Programmen genutzt, die viel mit Ein- und Ausgabe zu tun haben. Sie ermöglichen dem Programm, weiterzuarbeiten, selbst wenn es gerade auf Benutzereingaben, das Dateisystem oder das Netzwerk wartet. Der folgende Code definiert eine Methode
conread
(für concurrent read ), die ein Array mit Dateinamen erwartet und einen Hash zurückgibt, der diese Namen auf den Inhalt dieser Dateien abbildet. Die Methode verwendet Threads, um diese Dateien parallel zu lesen, und ist vor allem für die Verwendung mit dem Modul
open-uri
gedacht, das es ermöglicht, HTTP- und FTP-URLs mit
Kernel.open
so zu öffnen, als ob es Dateien wären:
# Dateien parallel lesen. Zusammen mit dem Modul "open-uri" für URLs nutzen.
# Ein Array mit Dateinamen übergeben. Gibt einen Hash aus Dateinamen und -inhalten
# zurück.
def conread(filenames)
h = {} # Leerer Hash für die Ergebnisse
# Einen Thread für jede Datei erzeugen
filenames.each do |filename| # Für jede angegebene Datei
h[filename] = Thread.new do # Erzeuge einen Thread, bilde ihn auf Dateinamen ab
open(filename) {|f| f.read } # Öffne und lies die Datei
end # Wert des Threads ist der Dateiinhalt
end
# Iteriere über den Hash und warte darauf, dass jeder Thread fertig ist.
# Ersetze den Thread im Hash durch seinen Wert (den Dateiinhalt).
h.each_pair do |filename, thread|
begin
h[filename] = thread.value # Dateinamen auf Dateiinhalt abbilden
rescue
h[filename] = $! # Oder auf die ausgelöste Exception
end
end
end
9.9.6.2 Ein Multithread-Server
Ein anderer, ähnlicher Anwendungsfall für Threads sind Server, die mit mehr als einem Client gleichzeitig arbeiten können. Wir haben gesehen, wie man das per
Kernel.select
hinbekommt, aber eine einfachere (wenn auch eventuell schlechter skalierende) Lösung nutzt Threads:
require 'socket'
# Diese Methode erwartet einen Socket, der mit einem Client verbunden ist.
# Sie liest Zeilen vom Client, dreht sie um und schickt sie zurück.
# Mehrere Threads können diese Methode gleichzeitig ausführen.
def handle_client(c)
while true
input = c.gets.chop # Eine Eingabezeile vom Client lesen
break if !input # Beenden, wenn keine Eingabe
break if input=="quit" # oder wenn der Client darum bittet
c.puts(input.reverse) # Ansonsten dem Client antworten
c.flush # Ausgabe des Ergebnisses erzwingen
end
c.close # Client-Socket schließen
end
server = TCPServer.open(2000) # An Port 2000 lauschen
while true # Server läuft in einer Endlosschleife
client =
Weitere Kostenlose Bücher