Die Programmiersprache Ruby (German Edition)
keines der oben beschriebenen Probleme haben.
Mutex
ist in Ruby 1.9 eine Core-Klasse und in Ruby 1.8 Teil der Standardbibliothek
thread
. Statt die Methoden
lock
und
unlock
explizit zu verwenden, ist es üblicher, die Methode
synchronize
zu nutzen und ihr einen Block zuzuordnen.
synchronize
sperrt das
Mutex
, führt den Code im Block aus und entsperrt das
Mutex
dann wieder in einer
ensure
-Klausel, so dass Exceptions korrekt verarbeitet werden. Hier sehen Sie ein einfaches Modell unseres Bankkontobeispiels, bei dem ein
Mutex
-Objekt genutzt wird, um den Thread-Zugriff auf die gemeinsam genutzten Kontodaten zu synchronisieren:
require 'thread' # Für Mutex-Klasse Ruby 1.8
# Ein BankAccount hat einen Namen, ein Girokonto und ein Sparkonto.
class BankAccount
def init(name, checking, savings)
@name,@checking,@savings = name,checking,savings
@lock = Mutex.new # Zur Thread-Sicherheit
end
# Sperre Konto und überweise Geld vom Sparbuch zu Girokonto
def transfer_from_savings(x)
@lock.synchronize {
@savings -= x
@checking += x
}
end
# Sperre Konto und gibt aktuelle Beträge aus
def report
@lock.synchronize {
"#@name\nGirokonto: #@checking\nSparkonto: #@savings"
}
end
end
9.9.7.1 Deadlock
Wenn wir
Mutex
-Objekte zur Thread Exclusion nutzen, müssen wir aufpassen, keine Deadlocks zu erzeugen. Deadlock ist die Bedingung, die eintritt, wenn alle Threads darauf warten, eine Ressource nutzen zu können, die von einem anderen Thread gesperrt wird. Da alle Threads geblockt sind, können sie die eigenen Sperren nicht freigeben. Und weil sie die Sperren nicht freigeben können, kann kein anderer Thread diese Sperren anfordern.
Ein klassisches Deadlock-Szenario besteht aus zwei Threads und zwei
Mutex
-Objekten. Thread 1 sperrt
Mutex
1 und versucht dann,
Mutex
2 zu sperren. Währenddessen sperrt Thread 2
Mutex
2 und versucht dann,
Mutex
1 zu sperren. Kein Thread kann die erforderliche Sperre anfordern und kein Thread kann die Sperre freigeben, die der andere benötigt, daher werden beide Threads für immer geblockt:
# Klassischer Deadlock: zwei Threads und zwei Sperren
require 'thread'
m,n = Mutex.new, Mutex.new
t = Thread.new {
m.lock
puts "Thread t hat Mutex m gesperrt"
sleep 1
puts "Thread t wartet darauf, Mutex n zu sperren"
n.lock
}
s = Thread.new {
n.lock
puts "Thread s hat Mutex n gesperrt"
sleep 1
puts "Thread s wartet darauf, Mutex m zu sperren"
m.lock
}
t.join
s.join
Um solche Formen von Deadlocks zu vermeiden, muss man Ressourcen immer in der gleichen Reihenfolge sperren. Wenn der zweite Thread
m
vor
n
gesperrt hätte, wäre der Deadlock nicht aufgetreten.
Beachten Sie, dass ein Deadlock auch ohne die Verwendung von
Mutex
-Objekten möglich ist. Ein Aufruf von
join
für einen Thread, der
Thread.stop
aufruft, wird zu einem Deadlock beider Threads führen, solange es nicht einen dritten Thread gibt, der den gestoppten Thread aufwecken kann.
Manche Ruby-Implementierungen können einfache Deadlocks wie diese erkennen und brechen dann mit einem Fehler ab, aber es gibt keine Garantie dafür.
9.9.8 Queue und SizedQueue
Die Standardbibliothek
thread
definiert die Datenstrukturen
Queue
und
SizedQueue
speziell für parallele Programmierung. Sie implementieren Thread-sichere FIFO-Queues und sind für ein Producer/Consumer-Modell bei der Programmierung gedacht. Mit diesem Modell erzeugt ein Thread Werte einer bestimmten Art und steckt sie mit der Methode
enq
( enqueue ) oder ihrem Synonym
push
in eine Queue. Ein anderer Thread »konsumiert« diese Werte und entfernt sie mit der Methode
deq
( dequeue ) bei Bedarf aus der Queue. (Die Methoden
pop
und
shift
sind Synonyme für
deq
.)
Das Entscheidende an
Queue
, das die Klasse für die parallele Programmierung nützlich macht, ist, dass die Methode
deq
blockiert, wenn die Queue leer ist, und wartet, bis der Producer-Thread der Queue einen Wert hinzufügt. Die Klassen
Queue
und
SizedQueue
implementieren die gleiche grundlegende API, aber die Variante
SizedQueue
hat eine maximale Größe. Wenn die Queue schon »voll« ist, blockiert die Methode zum Hinzufügen eines Werts zur Queue, bis der Consumer-Thread einen Wert aus der Queue entfernt hat.
Wie bei Rubys anderen Collection-Klassen können Sie die Anzahl von Elementen in einer Queue über
size
oder
length
ermitteln und mit
empty?
herausfinden, ob eine Queue leer ist. Die maximale Anzahl von Einträgen einer
SizedQueue
legen Sie beim Aufruf von
SizedQueue.new
fest. Nach dem Erzeugen
Weitere Kostenlose Bücher