Monitor (Informatik)
Ein Monitor in der Informatik ist ein programmiersprachliches Konzept zur Synchronisation von Zugriffen zeitlich verschränkt oder parallel laufender Prozesse oder Threads auf gemeinsam genutzten Datenstrukturen oder Ressourcen. Inkonsistente Zustände der Datenstrukturen werden vermieden, ohne dass Programmierer Synchronisationsprimitive wie z. B. Semaphore explizit nutzen müssen. Ein wechselseitiger Ausschluss bei Zugriffen auf die gemeinsam genutzte Datenstruktur wird erreicht, indem ein Compiler bei der Übersetzung eines Programmteils, das vom Programmierer mit Elementen der Programmiersprache als Monitor gekennzeichnet wurde, entsprechende Synchronisationsprimitive einfügt. Das Konzept wird z. B. von den Programmiersprachen Ada, Modula, Concurrent Pascal oder Java realisiert.
Das Monitorkonzept
Bei parallelem oder zeitlich verzahntem Ablauf von Prozessen (oder Threads) treten Situationen auf, in denen Datenstrukturen oder Ressourcen, die von den Prozessen gemeinsam genutzt werden, in inkonsistente Zustände geraten können, wenn nicht ein wechselseitiger Ausschluss vorgenommen wird. Synchronisationsprimitive wie Semaphore werden eingesetzt, die ausschließen, dass mehrere Prozesse gleichzeitig oder irgendwie zeitlich verzahnt die gemeinsam genutzten Datenstrukturen verändern. Korrekte Anwendung durch Programmierer vorausgesetzt, garantieren diese Primitive, dass innerhalb eines Zeitraums nur ein Prozess verändernd auf die Datenstruktur zugreift. Da die korrekte Anwendung aber durch Eigenschaften der Synchronisationsprimitive erschwert wird, entwickelten 1974 C.A.R. Hoare und 1975 Per Brinch Hansen ein auf höherem Abstraktionsniveau angesiedeltes Synchronisationsmittel, den Monitor.
Ein Monitor ist ein Modul (ein abstrakter Datentyp, eine Klasse), in dem die von Prozessen gemeinsam genutzten Daten und ihre Zugriffsprozeduren (oder Methoden) zu einer Einheit zusammengeführt sind. Zugriffsprozeduren mit kritischen Abschnitten auf den Daten werden als Monitor-Operationen speziell gekennzeichnet. Zugriffsprozeduren ohne kritische Abschnitte können vom Modul zusätzlich angeboten werden.
Die Monitor-Operationen werden unter wechselseitigem Ausschluss ausgeführt, ohne dass im Programmcode Synchronisationsanweisungen notiert werden müssen. Ein Programmierer kann sich somit auf die Funktionalität des Moduls konzentrieren und das Synchronisationsproblem außer Acht lassen. Bei Benutzung sorgt ein Monitor selbständig dafür, dass seine Monitor-Operationen immer nur von einem Prozess ausgeführt werden. Sollte ein Prozess A eine Monitor-Operation aufrufen, während diese oder eine andere Monitor-Operation bereits von einem Prozess B ausgeführt wird, so wird der Prozess A blockiert. Wechselseitiger Ausschluss, Blockieren und Deblockieren eines Prozesses werden mittels Synchronisationsprimitive erreicht, die bei Übersetzung des Monitors eingefügt werden.
Ein Monitor wird oft als ein Raum angesehen, in dem nur ein Akteur (Prozess) Platz findet. Wollen weitere Akteure in den Monitorraum, so müssen sie warten, bis im Monitorraum Platz frei geworden ist.
Bedingungssynchronisation
Kooperationssituationen (s. z. B. Erzeuger/Verbraucher-Problem), in denen ein Prozess während der Ausführung einer Monitor-Operation feststellt, dass die Datenstruktur sich in einem Zustand befindet, der eine weitere Ausführung nicht sinnvoll erscheinen lässt, können mit dem Monitor der beschriebenen Form nicht behandelt werden. Ohne weitere Synchronisationsmechanismen müsste der Prozess die Monitor-Operation beenden und den Monitor verlassen. Er muss dann später noch einmal die Operation aufrufen, um zu prüfen, ob der Zustand diesmal der erwartete ist. Dies läuft auf ein unerwünschtes mehrfaches Prüfen und Warten hinaus.
Monitore bieten daher eine Möglichkeit der Synchronisation von Aktivitäten innerhalb des Monitors. Beim Entwurf des Monitors und seiner Operationen werden Bedingungen definiert, die erfüllt sind oder nicht. Bedingungen werden mittels Bedingungsvariablen (condition variables) repräsentiert. Auf Bedingungsvariablen sind zwei Operationen definiert: wait() und signal(). Wenn ein Prozess während der Ausführung einer Monitor-Operation für eine Bedingungsvariable b die Operation wait() aufruft, wird der Prozess blockiert und außerhalb des Monitors in eine Warteschlange zu dieser Bedingungsvariable eingefügt. Da nun kein Prozess mehr im Monitor aktiv ist, kann ein anderer Prozess den Monitor von außen betreten und eine Monitor-Operation ausführen. Der blockierte Prozess wird deblockiert, wenn ein anderer Prozess während seiner Ausführung einer Monitor-Operation die Operation signal() an der Bedingungsvariablen b aufruft. Wenn zum Zeitpunkt des Aufrufs von signal() kein Prozess an der Bedingungsvariablen b wartet, ist der Aufruf ohne Auswirkung.
Ein Monitor, der über die Fähigkeit zur Bedingungssynchronisation verfügt, besteht aus einer geschlossenen Einheit von Daten und Prozeduren. Er besitzt oftmals eine implizite Lock-Variable und eine Warteschlange (die Monitor-Warteschlange) sowie eine beliebige Anzahl von Bedingungsvariablen. Jeder Bedingungsvariable ist eine weitere Warteschlange zugeordnet.
Aufbau eines Monitors mit Bedingungssynchronisation
Wird mittels signal an einer Bedingungsvariablen ein Prozess aus der Warteschlange zur Bedingungsvariablen deblockiert, so könnte dieser die Ausführung der Monitor-Operation an der Stelle, an der er das wait abgesetzt hat, fortsetzen, wenn sich nicht noch der signalisierende Prozess im Monitor befinden würde. Zwei Formen der Behandlung dieser Situation wurden entwickelt.
Hoare-Typ
Beim signal-Aufruf wird geprüft, ob die Warteschlange der Bedingungsvariablen Prozesse enthält. Falls diese nicht leer ist, wird der signalisierende Prozess blockiert und in die Monitor-Warteschlange eingetragen. Ein Prozess aus der Warteschlange der Bedingungsvariablen wird deblockiert. Der signalisierende Prozess wird demnach i. d. R. fortgesetzt, nachdem der deblockierte Prozess den Monitor verlassen hat. Hoare-Monitore werden auch Signal and Wait genannt.
Mesa-Typ
Neben dem Hoare-Typ gibt es noch den Mesa-Monitor-Typ, der Ende der 1970er Jahre von einer Gruppe bei Xerox entwickelt wurde. Im Gegensatz zum Hoare-Typ blockiert signal den signalisierenden Prozess nicht. Dieser wird stets fortgesetzt. signal reiht stattdessen einen Prozess von der Warteschlange der Bedingungsvariablen in die Monitor-Warteschlange um. Mesa-Typ-Monitore werden auch Signal-and-Continue-Monitore (etwa: signalisieren und fortfahren) genannt.
Monitore in Java
In Java verfügt jedes Objekt prinzipiell über Monitorfähigkeiten. Methoden einer Klasse, die kritische Abschnitte auf Attributen implementieren, sind mittels des Schlüsselworts synchronized als Monitor-Operationen zu kennzeichnen:
class Something { private SomeType sharedData;
public synchronized void fct1 (...) { ... } public synchronized void fct2 (...) { ... } }
Jedes Objekt der Klasse agiert dann als Monitor für seine Attribute. Aufrufe von synchronized-Methoden von mehreren Threads am selben Objekt werden unter wechselseitigem Ausschluss ausgeführt: zu jedem Zeitpunkt greift höchstens ein Thread im Rahmen einer synchronized-Methode auf die Objektattribute zu.
Die Sprache Java sieht keine Bedingungsvariablen vor. Für die Bedingungssynchronisation sind in der Klasse Object folgende Methoden definiert:
- wait()
blockiert den aufrufenden Thread und gibt den Monitor – das Objekt, dessen synchronized-Methode er gerade ausführt – frei. - notify()
deblockiert (irgend)einen an diesem Monitor blockierten Thread; dieser kann weiterlaufen, sobald der Monitor frei ist. (Dieses Verhalten ist nicht „fair“ im Sinne von Fairness.) - notifyAll()
deblockiert alle an diesem Monitor blockierten Threads; sie können weiterlaufen, sobald der Monitor frei ist.
Wegen der fehlenden Bedingungsvariablen muss ein deblockierter Thread die Bedingung, auf die er wartet, erneut prüfen – sie könnte noch nicht gültig sein oder schon durch schnellere Threads wieder invalidiert worden sein.