Sascha Kretzschmann - Institut für Informatik

Werbung
Fachbereich Mathematik und Informatik
Institut für Informatik
Proseminar Nichtsequentielle Programmiersprachen
WS 2011/2012
Monitore
Sascha Kretzschmann
2. November 2011
Inhaltsverzeichnis
1 Einleitung
2
2 Monitore nach Brinch Hansen / Hoare
3
2.1
Vorüberlegung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
2.2
Monitordefinition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
2.3
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
3 Monitoren in Java
6
3.1
Umsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
3.2
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
4 Anwendungsbeispiel
9
5 Quellen
12
1
1 Einleitung
Für nichtsequenzielle Programmierung ist das wichtigste Sicherheitskriterium die Gewährleistung, dass sich Prozesse beim Zugriff auf Variablen nicht gegenseitig interferieren, also beeinflussen. Das bedeutet ferner, dass jeder Zugriff auf gemeinsam genutzte
Variablen oder Ressourcen unbedingt von Sperren (locks), Semaphoren o.Ä. überwacht
werden muss.
Ein Semaphor ist dabei eine Datenstruktur, welches einen fundamentalen Mechanismus zur Synchronisation darstellt. Sie wurden als Verallgemeinerung und Abstraktion
von Spin-Locks von Edsger W. Dijkstra vorgeschlagen. Sie können sehr leicht und systematisch dazu benutzt werden, das Problem des gegenseitigen Ausschlusses (mutual
exclusion) zu lösen. Jedoch birgt dies einige Probleme und Gefahren.
Bei unachtsamer Benutzung kann es nämlich zu Fehlern kommen, die eine kontrollierte
Ausführung des Programmes nicht mehr gewährleisten, da es sich lediglich um einen
Mechanismus auf niedriger Ebene handelt. Als Beispiele sind dabei, das Vertauschen der
P- und V-Operation oder das zu häufige Benutzen derer, anzuführen.
Um dieses Problem zu beheben, führte Per Brinch Hansen Monitore als Programmierkonzept ein.[1] Dieses wurde später von C.A.R Hoare populär gemacht.[5]
Im ersten Teil meiner Ausarbeitung werde ich besonders auf die Umsetzung von Monitoren nach Brinch Hansen / Hoare eingehen. Der zweite Teil soll darstellen, wie das
ursprüngliche Monitor-Konzept in Java umgesetzt wird, bzw. Unterschiede herausarbeiten. Am Ende skizziere ich noch ein einfaches Beispiel.
2
2 Monitore nach Brinch Hansen / Hoare
2.1 Vorüberlegung
Per Brinch Hansen wollte die sequentielle Programmiersprache Pascal um nichtsequentielle Elemente erweitern.[1] Sein Hauptanliegen war dabei, ein Sprachkonzept zu entwickeln, indem parallele Prozesse Daten in einem gemeinsamen Speicher teilen können.
Mehrere Prozesse sollen also Daten gemeinsam nutzen können. Dazu betrachten wir
einen Prozess detaillierter.
Ein Prozess besteht aus Zugriffsrechten, einer privaten Datenstruktur und einem sequentiellen Programm, welches abgearbeitet wird. Offensichtlich arbeitet es auf seinen
privaten Daten. Jedoch kann ein Prozess nicht auf die private Datenstruktur eines anderen Prozesses zugreifen.
Wie können also konkurrierende Prozesse Daten gemeinsam nutzen, zum Beispiel über
einen gemeinsamen Puffer? Die Lösung ist zunächst trivial.
Es existiert ein Puffer und zwei Prozesse in Form eines producer process und eines
consumer process, wobei der Produzent Daten erzeugt und diese im Puffer ablegt und
der Konsument diese aufruft.[1] Folglich können konsumierende Prozesse Daten aufrufen
und verarbeiten, solange diese existieren. Produzierende Prozesse hingegen stellen Daten zur Verfügung, solange der gemeinsam benutzte Puffer nicht voll ist. Doch wie soll
jetzt der Puffer umgesetzt werden, so dass gegenseitiger Ausschluss (mutual exclusion)
sichergestellt wird? Die Lösung sind Monitore.
2.2 Monitordefinition
Ein Monitor ist ein Konstrukt, welches parallele Prozesse synchronisiert und ggf. die
Zugriffsreihenfolge kontrolliert. D.h. es wird mutual exclusion gewährleistet, so dass zu
jeder Zeit immer nur genau ein Prozess Code einer Prozedur ausführt.[3] Ein Monitor
definiert Variablen, auf die man nur über die deklarierten Monitor-Prozesse zugreifen
kann. Zusätzlich gibt es die Möglichkeit, Bedingungsvariablen (condition variables) zu
definieren.
3
2 Monitore nach Brinch Hansen / Hoare
Auf diese können nun folgende Operationen ausgeführt werden:
1. wait(c): warte, bis ein Prozess signalisiert, dass Bedingung c erfüllt ist
2. signal(c): signalisiere einen Prozess, der gerade auf c wartet
3. signalAll(c): wie signal(c), nur, dass alle Prozesse signalisiert werden
4. empty(c): überprüfe, ob ein Prozess gerade auf Bedingung c wartet
Beim Signalisieren von Prozessen kann man weiterhin vier verschiedene Möglichkeiten
des Fortführens unterscheiden:
1. Signal-and-continue: Der Signalisierende fährt fort und der aufgeweckte Prozess
ist zu einem späteren Zeitpunkt dran, wenn Monitor frei ist.
2. Signal-and-wait: Der signalisierende Prozess wartet und ist zu einem späteren
Zeitpunkt dran. Der aufgeweckte Prozess kann nun arbeiten.
3. Signal-and-urgent-wait: wie oben, nur das dem signalisiertem Prozess garantiert
wird, dass er nach dem aufgeweckten Prozess dran wird (wird vorne an die Queue
gesetzt)
4. Signal-and-return: Die Signalisierung ist nur mit dem Verlassen eines Prozesses
aus dem Monitor verbunden.
Wollen wir uns nun einmal verschiedene "Typen"von Monitoren anschauen, die teilweise die eben genannten Varianten des Signalisierens umsetzen. Es existieren nämlich
verschiedene Möglichkeiten, wie Prozesse in einem Monitor verwaltet werden. Zu den
populärsten gehört einmal die Variante nach Hoare:
Hoare style monitor[3]
4
2 Monitore nach Brinch Hansen / Hoare
Diese Art von Monitoren besitzt eine Eingangswarteschlange und eine Warteschlange
für jede Bedingungsvariable. Sobald ein Prozess den Monitor betritt, wird er zunächst
in die Eingangswarteschlange eingereiht. Dort verbleibt er, solange ein anderer Prozess
Code in der kritischen Sektion ausführt. Wird zum Beispiel wait(s) aufgerufen, landet
der Prozess in der Warteschlange für die Bedingungsvariable s.
Die zweite Variante stammt von Mesa:
Mesa style monitor[3]
Mesa benutzt die signal-and-continue Variante mit einer FIFO Warteschlangen. Im
Gegensatz zu Hoare, landen also signalisierte Prozesse zunächst einmal wieder in der
Eingangswarteschlange. Auch Java Monitore sind eine Variation dieses Types.
2.3 Zusammenfassung
Im vorherigen wurden Monitore beschrieben und gezeigt, wie diese zusammen mit
Klassen und Prozessen in Concurrent Pascal umgesetzt wurden. Dabei sollten sich
einige entscheidende Punkte herausgestellt haben.
1. Monitore sind historisch das erste nicht triviale Sprachkonzept für Synchronisation.
2. Jede condition variable bringt eine eigene Warteschlange, in der Prozesse separat
verzögert werden können, um auf bestimmte Bedingungen zu warten.
3. Wird eine Bedingung erfüllt, bekommen wartende Prozesse ein Signal.
4. Dabei gibt es vier verschiedene Varianten des Signalisierens, die Einfluss auf die
Ausführungsreihenfolge haben (können).
5
3 Monitoren in Java
3.1 Umsetzung
Um in Java auch Nichtsequenzialität benutzen zu können, wurden Threads eingeführt.
Ein Thread kann man sich als ein abgekapseltes Objekt vorstellen, welches sequenziellen
Code abarbeitet.
In Java existiert kein Konstrukt, mit dem man Monitore explizit erzeugen kann. Somit
kann man so gut wie jedes Objekt als einen Monitor verwenden. Jedoch wird dann kein
gegenseitiger Ausschluss garantiert. Wir wollen uns also zu Beginn erst einmal anschauen,
was alles getan werden muss, um "normale"Java Klassen in eine Java Monitor Klasse zu
überführen.
1. Instanzvariablen der Klassen müssen private deklariert sein
2. Methoden müssen als synchronized deklariert werden
3. Bedingte Synchronisation mit wait(), notify() und notifyAll()
Folglich unterscheidet sich die Umsetzung von Monitoren in Java stark, von der eigentlichen Auffassung nach Brinch Hansen/Hoare.[1] Das macht die ganze Sache relativ
unsicher und garantiert keinen leichten und sicheren Umgang mehr, so wie es ursprünglich gedacht war.
Weiterhin existieren keine Bedingungsvariablen. Stattdessen hat jedes Objekt einen
Lock, der nur gehalten werden kann, wenn die Methoden synchronized deklariert
wurden.[4] Neben einer Eingangswarteschlange (entry queue) existiert lediglich eine wait
queue, in der alle wartenden Threads verwaltet werden.
6
3 Monitoren in Java
Java style monitor[3]
Nun kann es aber sein, dass nach der Prüfung einer bestimmten Bedingung, ein Thread
verzögert werden muss. Dazu werden die oben genannten drei Methoden zur bedingten
Synchronisation verwendet. Diese wären (sei o eine Ausprägung vom Typ Object):
1. o.wait(): lock von o wird freigegeben, Thread gelangt in Queue
2. o.release(): weckt einen Thread auf, der dann wartet, bis er Lock von o bekommt
3. o.releaseAll():wie release(), nur werden alle Threads aufgeweckt
Mit anderen Worten, man synchronisiert also über die Locks der Objekte. Und das
kann man sich so vorstellen. Beim Aufruf einer synchronized Methode, eignet (acquire)
sich der Thread den Lock an und kann unter gegenseitigem Ausschluss die gemeinsam
genutzten Daten manipulieren. Danach wird der Lock wieder freigegeben.[4]
3.2 Zusammenfassung
Abschließend noch einmal zusammengefasst, inwiefern sich das Monitorkonzept in Java
vom "klassischen"Monitor nach Brinch Hansen insbesondere unterscheidet.
1. In Java existiert kein Monitorkonstrukt, sowie keine Bedingungsvariablen.
2. Die Umsetzung eines Monitors in Java ist immer eine Variation des Monitores nach
Mesa mit signal-and-continue.
7
3 Monitoren in Java
3. Bedingungsvariablen können simuliert werden, indem über das Lock von Objekten
synchronisiert wird.
4. Sind jedoch alle Methoden einer Klasse synchronized deklariert, entspricht dies der
klassischen Monitorvariante.
5. Java Monitore sind eine Variante nach Mesa, mit signal-and-continue.
6. Fairness wird in Java nicht "betrachtet", anders gesagt kann darüber keine Aussage
getroffen werden.
8
4 Anwendungsbeispiel
Hier die Umsetzung eines Bounded Buffers in Concurrent Pascal und in Java:
bounded b u f f e r : monitor
begin b u f f e r : array O . . N − 1 of p o r t i o n ;
lastpointer : O. .N − 1;
count : O . . N;
nonempty , n o n f u l l : c o n d i t i o n ;
procedure append ( x : p o r t i o n ) ;
begin i f count = N then n o n f u l l . w a i t ;
n o t e 0 <= count < N;
b u f f e r [ l a s t p o i n t e r ] := x ;
l a s t p o i n t e r := l a s t p o i n t e r o l ;
count := count + 1 ;
nonempty . s i g n a l
end append ;
procedure remove ( r e s u l t x : p o r t i o n ) ;
begin i f count = 0 then nonempty . w a i t ;
n o t e 0 < count < N;
x := b u f f e r [ l a s t p o i n t e r o count ] ;
nonfull . signal
end remove ;
count := 0 ; l a s t p o i n t e r := 0 ;
end bounded b u f f e r ;
Bounded buffer in Java[5]
Es gibt Produzenten, die Daten erzeugen und diese an Konsumenten weitergeben.Die Weitergabe erfolgt mit Hilfe des Buffers, der eine bestimmte Anzahl von Daten lagern kann. Es muss
darauf geachtet werden, dass der Produzent wartet, sollte der Buffer voll sein.
Hierfür existiert die Bedingungsvariable nonfull. Sollte der Puffer also voll sein, dann wartet
der Prozess in der Warteschlange der Bedingungsvariable. Er wird erst wieder signalisiert, wenn
ein Prozess die Methode remove() aufgerufen hat, und somit wieder etwas prouziert werden kann.
9
4 Anwendungsbeispiel
Außerdem sollen Konsumenten, die einen leeren Buffer vorfinden, ebenfalls warten. Für diesen
Fall gibt es die Bedingungsvariable nonempty. Das Prinzip ist das selbe, wie bei nonfull.
Im folgenden sehen wir nun die Umsetzung des Bounded Buffers in Java:
class Buffer
{
private Object [ ] b u f f e r ;
// Das O b j e c t b u f f e r h a t d i e i n d i z e s i n und o u t
private int in , out , count , s i z e ;
public synchronized void append ( Object
{
while ( count == s i z e ) w a i t ( ) ;
//
buffer [ in ] = o ;
in = ( in + 1) % s i z e ;
//
++count ;
//
notifyAll ();
//
}
public synchronized Object remove ( )
{
while ( count == 0 ) w a i t ( ) ;
Object o = b u f f e r [ out ] ;
b u f f e r [ out ] = null ;
out = ( out + 1 ) % s i z e ;
−−count ;
notifyAll ();
return o ;
}
o)
Buffer i s t v o l l
Produziert
Erhoehe c o u n t e r
Wecke a l l e wartenden Threads
// B u f f e r i s t l e e r
// Konsumiert
// Senke c o u n t e r
// Wecke a l l e wartenden Threads
public B u f f e r ( )
{
t h i s . i n = 0 ; t h i s . out = 0 ; t h i s . count = 0 ; t h i s . s i z e = 1 0 ;
}
}
Bounded buffer in Java[4]
Bei dieser Umsetzung existiert nur eine Warteschlange, nämlich die implizize Bedingungsvariable des Objektes (wait queue). Da nun beim Signalisieren nicht auf spezielle Warteschlangen
10
4 Anwendungsbeispiel
zugegriffen werden kann (alle befinden sich in einer), werden alle wartenden Prozesse mit der
releaseAll()-Methode aufgeweckt und nach dem Prinzip signal-and-continue behandelt.
Ein weiterer Unterschied ist, dass die Bedingungen über while-Schleifen überprüft werden.
Es kann nämlich passieren, dass ohne Aufruf der releaseAll()-Methode ein wartender Prozesse
aufgeweckt wird. Um Probleme durch dieses unvorhersehbare Verhalten zu umgehen, haben wir
die ebend erwähnte Überprüfung umgesetzt (covering condition).
11
5 Quellen
Brinch Hansen, P., 1975a. The programming language Concurrent Pascal. IEEE Transactions
on Software Engineering 1, June, 199-207; http://dl.acm.org/citation.cfm?id=762983 [1]
Brinch Hansen, P., Java’s insecure parallelism, SIGPLAN Notices 34, 4 (April 1999), 38-45.
Copyright 1999, http://www.margull.com/downloads/PBHansenParallelism.pdf [2]
Kyas, Prof. Dr. Marcel 2011, Manuskript zur Vorlesung ALP4, Freie Universität Berlin,
Abschnitt über Monitore [3]
Gorlatsch, Sergei 2006, Multithreating and Networking im Java-Umfeld Volresung 4, Uni
Münster, 11-24; http://pvs.uni-muenster.de/pvs/lehre/WS08/mnj/folien/mnj4.pdf [4]
C.A.R. Hoare: Monitors – An Operating System Structuring Concept. Comm. of the
ACM 17.10, October 1974; ftp://reports.stanford.edu/pub/cstr/reports/cs/tr/73/401/CS-TR73-401.pdf [5]
12
Herunterladen