Download Diplomarbeit - Institut für Nachrichtentechnik

Document related concepts
no text concepts found
Transcript
Fachhochschule Köln
University of Applied Sciences Cologne
09
Fachbereich Nachrichtentechnik
Institut für Informatik
Diplomarbeit
Synchronisation und Kommunikation
von Prozessen und Threads in
Java, Windows NT 4.0 und Unix / Linux
Student :
Stefan Krumbach
Matr. Nr.:
11012566 11
Referent :
Prof. Dr. C. Vogt
Korreferent :
Abgabedatum :
Prof. Dr. D. Rosenthal
November 2001
Hiermit versichere ich, daß ich die Diplomarbeit selbständig angefertigt und keine
anderen als die angegebenen und bei Zitaten kenntlich gemachten Quellen und
Hilfsmittel benutzt habe.
( Stefan Krumbach )
2
Inhaltsverzeichnis
1 Einleitung ......................................................................................................................7
1.1 Inhalte dieser Diplomarbeit .................................................................................8
1.2 Synchronisation von Prozessen / Threads..........................................................9
1.3 Kommunikation zwischen Prozessen / Threads..................................................9
1.4 Deadlock ..........................................................................................................10
2 Programmiersprache Java.........................................................................................11
2.1 Grundlegende Eigenschaften ...........................................................................11
2.2 Prozesse / Threads in Java ..............................................................................12
2.3 Zustände von Threads......................................................................................12
2.4 Priorität und Scheduling....................................................................................14
2.5 Dämon-Threads................................................................................................14
2.6 Thread Gruppen ...............................................................................................15
2.7 Erzeugung und Steuerung von Threads ...........................................................16
2.8 Synchronisation ................................................................................................20
2.8.1 Synchronisation durch Priorität .............................................................20
2.8.2 Synchronisation durch Monitore ...........................................................20
2.8.3 Synchronisation durch Sperr-Objekte ...................................................21
2.8.4 Synchronisation durch Klassenbezogene-Sperre .................................23
2.8.5 Synchronisation durch Ereignisse.........................................................25
2.8.6 Synchronisation auf das Ende von anderen Threads............................26
2.9 Kommunikation zwischen Threads mit Piped-Streams .....................................28
3 Betriebssytem Windows NT 4.0.................................................................................32
3.1 Grundlegende Eigenschaften ...........................................................................32
3.2 Prozesse / Threads in Windows NT..................................................................32
3.3 Zustände von Prozessen / Threads ..................................................................33
3.4 Scheduling........................................................................................................34
3.5 Erzeugung und Steuerung von Prozessen / Threads........................................35
3.6 Synchronisation von Threads ...........................................................................41
3.6.1 Synchronisation durch Priorität .............................................................41
3.6.2 Synchronisation durch kritische Bereiche .............................................41
3.6.3 Synchronisation durch Ereignisse.........................................................43
3.6.4 Synchronisation durch Mutex................................................................49
3.6.5 Synchronisation durch Semaphore .......................................................51
3
3.7 Kommunikation.................................................................................................53
3.7.1 Unbenannte Pipes ................................................................................55
3.7.2 Benannte Pipes ....................................................................................56
4 Betriebssystem Unix / Linux......................................................................................62
4.1 Grundlegende Eigenschaften ...........................................................................62
4.2 Prozesse / Threads in Unix / Linux ...................................................................62
4.3 Zustände von Prozessen ..................................................................................63
4.4 Priorität und Scheduling....................................................................................64
4.5 Erzeugung und Steuerung von Prozessen........................................................65
4.6 Hintergrundprozesse "Dämons"........................................................................69
4.7 Synchronisation ................................................................................................69
4.7.1 Synchronisation durch Semaphore .......................................................69
4.7.2 Synchronisation durch Signale .............................................................73
4.7.3 Wechselseitiger Ausschluß mit Spinlocks .............................................75
4.8 Prozesskommunikation.....................................................................................76
4.8.1 Pipes ....................................................................................................76
4.8.2 Message Queues .................................................................................81
4.8.3 Shared Memory ....................................................................................82
4.8.4 Sockets ................................................................................................82
4.8.5 Streams................................................................................................84
5 Beispiel Applet............................................................................................................85
5.1 Grundlegende Arbeitsweise eines Applets........................................................85
5.2 Eigenschaften des Beispiel Applets ..................................................................86
5.3 Klassen des Applets .........................................................................................89
5.3.1 Die Klasse Applet1.java........................................................................89
5.3.2 Die Klasse Beenden.java......................................................................93
5.3.3 Die Klasse Counter.java .......................................................................94
5.3.4 Die Klasse CountingThread.java ..........................................................94
5.3.5 Die Klasse CountingThreadRunnable.java ...........................................96
5.3.6 Die Klasse ImageCanvas.java ..............................................................97
5.3.7 Die Klasse IntJTextField.java................................................................98
5.3.8 Die Klasse MeineTextArea.java............................................................99
5.3.9 Die Klasse Steuerung.java .................................................................100
5.3.10 Die Klasse Syncprio.java ..................................................................101
4
6 Beispielprogramme ..................................................................................................102
6.1 Beispielprogramme für Java ...........................................................................103
6.1.1 Counter ..............................................................................................103
6.1.2 Thread................................................................................................104
6.1.3 Zwei Threads......................................................................................106
6.1.4 Priorität...............................................................................................108
6.1.5 Monitore .............................................................................................111
6.1.6 Sperr-Objekt .......................................................................................114
6.1.7 Ereignis ..............................................................................................118
6.1.8 Reihenfolge ........................................................................................122
6.1.9 Pipe ....................................................................................................125
6.2 Beispielprogramme für Windows NT...............................................................128
6.2.1 Counter ..............................................................................................128
6.2.2 Thread................................................................................................129
6.2.3 Zwei Threads......................................................................................131
6.2.4 Priorität...............................................................................................133
6.2.5 Ereignis ..............................................................................................136
6.2.6 Semaphor...........................................................................................139
6.2.7 Philosophen .......................................................................................142
6.2.8 Ereignis für zwei Prozesse .................................................................145
6.2.9 Unbenannte Pipe................................................................................147
6.2.10 Benannte Pipe eine Richtung ...........................................................150
6.2.11 Benannte Pipe zwei Richtungen .......................................................154
6.2.12 Benannte Pipe für zwei Prozesse .....................................................159
6.3 Beispielprogramme für Unix / Linux ................................................................163
6.3.1 Counter ..............................................................................................163
6.3.2 Prozess ..............................................................................................164
6.3.3 Zwei Prozesse ....................................................................................165
6.3.4 Semaphor...........................................................................................167
6.3.5 Signale ...............................................................................................170
6.3.6 Unbenannte Pipe................................................................................172
6.3.7 Benannte Pipe für zwei getrennte Prozesse .......................................174
7 Abschlussbetrachtung.............................................................................................177
5
8 Anhang ......................................................................................................................178
8.1 Literaturverzeichnis.........................................................................................178
8.2 Internetquellen................................................................................................178
8.3 Werkzeuge .....................................................................................................179
8.4 Source-Code des Beispiel Applets..................................................................180
8.4.1 Die Klasse Applet1.java......................................................................180
8.4.2 Die Klasse Beenden.java....................................................................198
8.4.3 Die Klasse Counter.java .....................................................................199
8.4.4 Die Klasse CountingThread.java ........................................................200
8.4.5 Die Klasse CountingThreadRunnable.java .........................................201
8.4.6 Die Klasse ImageCanvas.java ............................................................203
8.4.7 Die Klasse IntJTextField.java..............................................................204
8.4.8 Die Klasse MeineTextArea.java..........................................................205
8.4.9 Die Klasse Steuerung.java .................................................................207
8.4.10 Die Klasse Syncprio.java ..................................................................208
8.4.11 Implementierung des Beispiel Applets in HTML-Seite.......................209
6
1 Einleitung
1 Einleitung
Heutige Betriebssysteme und die Programmiersprache Java erlauben es Programmcode
von einer oder mehreren Anwendung(en) in mehrere Teile zu unterteilen und diese Teile
"parallel" auszuführen. Diese nebenläufigen Programme / Programmteile werden
Prozesse oder Threads genannt. Der Begriff "parallel" wird in dieser Diplomarbeit häufiger
verwendet. Er ist aber nur zum Teil richtig, da echte Nebenläufigkeit (parallele
Bearbeitung von Prozessen / Threads) nur in einem System mit mehreren Prozessoren
stattfinden kann.
Bei einem System mit einem Prozessor spricht man von einer Pseudo-Nebenläufigkeit.
Hierbei können die Prozesse / Threads nicht parallel ausgeführt werden. Für den
Anwender scheint es, als würden die Prozesse / Threads parallel ausgeführt, weil der
Prozessor schnell zwischen den einzelnen Prozessen / Threads umgeschaltet wird.
Diese Diplomarbeit erläutert die Synchronisation und Kommunikation von diesen
Prozessen / Threads in verschiedenen Umgebungen (Betriebssystem Unix / Linux,
Windows NT und der Programmiersprache Java).
Es ist notwendig genau zu unterscheiden was die einzelnen Betriebssysteme bzw. die
Programmiersprache unter diesen Begriffen verstehen und wie sie umgesetzt werden.
Die Art und Weise, wie die benötigten Funktionen bzw. Methoden in den einzelnen
Umgebungen erläutert werden, richtet sich nach den Fachbüchern der jeweiligen
Umgebung.
Im allgemeinen werden die Begriffe Prozess und Thread wie folgt definiert:
• Prozess (engl. process oder task) ist ein Kontext zusammen mit dem Programmcode.
• Thread oder Lightweight Process (leichtgewichtiger Prozess) ist eine Aktivität
(= Programmausführung) innerhalb eines Prozesses.
Eine genaue Erklärung, wie die einzelnen Umgebungen die Begriffe Prozess / Thread
umsetzen, findet in dem jeweiligen Kapitel der Umgebung statt.
Weitere Begriffe, die in diesem Zusammenhang oft genannt und daher hier kurz erläutert
werden, sind:
• Multitasking
Beim Multitasking wird ein Programm in mehrere
Ausführungseinheiten unterteilt. Diese Ausführungseinheiten
werden z.B. in Unix / Linux Systemen Kind-Prozesse genannt
und als eigenständige Prozesse behandelt.
• Multithreading
Bei Java und Windows NT kann ein Programm (Prozess) auch
in mehrere Ausführungseinheiten unterteilt werden. Diese
Ausführungseinheiten (Threads) sind für das jeweilige System
einfacher zu verwalten.
7
1 Einleitung
• Multiprogramming
Beim Multiprogramming ist es möglich mehrere eigenständige
Programme "parallel" auszuführen.
• Multiprocessing
Besitzt ein System mehr als einen Prozessor spricht man von
einem Multprocessing bzw. Multiprozessorsystem.
1.1 Inhalte dieser Diplomarbeit
Kapitel 1
beinhaltet außer der Einleitung und dieser Inhaltsangabe eine kurze
allgemeine Erläuterung von Synchronisation, Kommunikation und dem
Begriff Deadlock im Zusammenhang mit Prozessen / Threads.
Kapitel 2
zeigt, wie die Synchronisation und Kommunikation von Threads bei der
Programmiersprache Java realisiert wird.
Kapitel 3
veranschaulicht wie in Windows NT 4.0 Prozesse und Threads
synchronisiert werden können. Außerdem wird die Kommunikation mit Hilfe
von Pipes erläutert.
Kapitel 4
geht auf die Synchronisation und Kommunikation von Prozessen in dem
Betriebssystem Unix bzw. Linux ein. Des weiteren wird auf die
unterschiedlichen
Kommunikationsmöglichkeiten
der
Prozesse
eingegangen. Im Vordergrund steht aber auch in diesem Kapitel die
Kommunikation mit Pipes.
Kapitel 5
erläutert ein Applet, daß für die HTML-Version dieser Diplomarbeit
programmiert wurde. Dieses Applet ermöglicht dem Anwender die
Auswirkungen
der
einzelnen
Synchronisationsmechanismen
auszuprobieren.
Kapitel 6
beinhaltet einfache Beispielprogramme für Java, Windows NT und Unix
bzw. Linux. Bei diesen Beispielprogrammen wurde der Schwerpunkt auf die
Synchronisation und Kommunikation der Prozesse und Threads gelegt.
Kapitel 7
zeigt eine Abschlussbetrachtung.
Anhang
Im Anhang ist das Literaturverzeichnis, die angewendeten Werkzeuge und
der Source-Code des Applets abgedruckt.
8
1 Einleitung
1.2 Synchronisation von Prozessen / Threads
Der eigentliche Sinn von seperaten Prozessen / Threads innerhalb einer Anwendung liegt
darin, daß verschiedene Aufgaben unabhängig von einander bearbeitet werden. In
einigen Fällen müssen die einzelnen Prozesse / Threads aber auch aufeinander
abgestimmt ( synchronisiert ) werden, z.B. wenn mehrere Prozesse / Threads sich ein
Betriebsmittel teilen oder ein Prozess / Thread auf das Ergebnis eines anderen
Prozesses / Threads warten muß. Diese Synchronisation kann auf verschiedene Arten
erfolgen. Jedes der Betriebssysteme bzw. die Programmiersprache Java bietet
verschiedene Mechanismen zur Synchronisation an, die später genauer erläutert werden.
Allgemein unterscheidet man bei der Synchronisation zwei Hauptarten:
1. Wechselseitiger Ausschluss
Bei dieser Art der Synchronisation wird sichergestellt, daß eine Ressource nur von
einem Prozess / Thread benutzt wird. Alle anderen Prozesse / Threads müssen
warten, bis die Ressource wieder freigegeben wird.
2. Reihenfolgebedingung
Bei dieser Art der Synchronisation wird die Reihenfolge festgelegt, in der die einzelnen
Prozesse / Threads abgearbeitet werden.
1.3 Kommunikation zwischen Prozessen / Threads
Obwohl Prozesse / Threads unabhängig von einander sind müssen sie Daten
untereinander austauschen können. Dies ist z.B. nötig, wenn ein Prozess / Thread mit
dem Ergebnis eines andern Prozesses / Threads arbeiten muß. Die einzelnen
Umgebungen bieten verschiedene Methoden an diese Kommunikation zu ermöglichen.
Eine Möglichkeit, die vom Grundprinzip in allen Umgebungen sehr ähnlich ist, ist die
Kommunikation mit Hilfe von Pipes. Aus diesem Grund wird diese Art der Kommunikation
zwischen verschiedenen Prozessen / Threads genauer erläutert.
Wie die Kommunikation mit Pipes in den einzelnen Umgebungen implementiert ist wird in
dem jeweiligen Kapitel der Umgebung genauer erklärt.
9
1 Einleitung
1.4 Deadlock
Ein Deadlock (offizielle deutsche Übersetzung: "Verklemmung") entsteht, wenn mehrere
Prozesse / Threads sich gegenseitig so beeinflussen das keiner von ihnen in der Lage ist
seine Aufgabe bis zum Ende auszuführen. Eine solche Behinderung entsteht besonders
schnell, wenn die Prozesse / Threads mehrere Ressourcen gleichzeitig benötigen. Die
Aufgabe der Synchronisation ist es einen solchen Deadlock zu verhindern.
Abb. 1.1: Das Philosophen-Problem [MiSi1999]
Um einen Deadlock anschaulich zu erläutern verwendet man in der Grundlagenliteratur
meist das Beispiel der denkenden und essenden Philosophen. Mehrere Philosophen
verbringen ihre Zeit mit Denken und Essen. Sie sitzen an einem runden Tisch, jeder
Philosoph hat einen Teller vor sich, und zwischen den Tellern liegt eine Gabel. Wenn ein
Philosoph essen will, benötigt er zwei Gabeln. Da er sich jedoch beide mit seinem
Nachbarn teilen muß, können nie zwei benachbart sitzende Philosophen gleichzeitig
essen. Während die Philosophen denken, sind sie ausschließlich mit sich selbst
beschäftigt und stehen nicht in Kontakt mit ihrer Umwelt. Wenn sie jedoch vom vielen
Denken hungrig geworden sind, müssen sie versuchen, zwei Gabeln zu bekommen, um
dann zu essen. Damit das Essen gerecht verteilt wird und kein Philosoph verhungert, muß
man sich eine Regelung für die Vergabe des Bestecks einfallen lassen. Man könnte auf
die Idee kommen, daß jeder Philosoph zunächst das Besteck zu seiner Rechten ergreift,
dieses also in Besitz nimmt, und dann darauf wartet, daß das entsprechende andere Teil
zu seiner Linken frei ist. Wenn dabei zufällig alle Philosophen gleichzeitig hungrig werden,
dann nimmt jeder das Teil zu seiner Rechten in Besitz. Damit hält jeder eine Gabel, und
auf dem Tisch ist kein Besteck mehr übrig. Alle Philosophen warten gegenseitig darauf,
daß ein anderer sein Besteck wieder ablegt. In diesem Zustand würden alle Philosophen
ewig warten und so verhungern. Man nennt diesen Zustand Deadlock. [MiSi1999]
10
2 Programmiersprache Java
2 Programmiersprache Java
2.1 Grundlegende Eigenschaften
Java wurde von der Firma Sun entwickelt und erstmals am 23. Mai 1995 als neue, objektorientierte und plattformunabhängige Programmiersprache vorgestellt. Durch die
Portabilität ist Java ideal für das Internet. Kleine Java Programme, die sogenannten
Applets, können durch ein eigens dafür definiertes "Tag" in HTML-Seiten eingebunden
werden und dadurch die statischen HTML- Seiten mit Leben erfüllen. Heutzutage werden
immer mehr Internetanwendungen mit Hilfe von Applets umgesetzt.
Einige Eigenschaften von Java sind:
Portabel
Java ist portabel. Dies wird dadurch erreicht, daß eine virtuelle
Maschine, die für alle heutigen Betriebssysteme erhältlich ist, den
kompilierten Bytecode der Java Anwendungen ausführt.
Objektorientiert
Java ist vollständig objektorientiert aufgebaut. Es ähnelt in weiten
Zügen der Programmiersprache C++.
Multithreaded
siehe Kapitel 2.2
Verteilt
Die Standardbibliothek von Java bietet bereits Klassen, mit denen
verteilte Anwendungen auf Basis von TCP/IP einfach realisiert
werden können.
Robust
Es gibt mehrere Eigenschaften von Java, die die Robustheit der
Anwendungen erhöht. Hierfür wurden auf einige Elemente verzichtet,
die bei der Programmiersprache C++ oft zu Fehler bzw. Instabilität
der Programme führen.
• Java verzichtet auf die Arbeit mit Pointern.
• Das System kümmert sich selbständig um die Freigabe von
Speicherplatz.
• Java ist streng
Typenprüfung.
Sicher
objektorientiert
mit
gleichzeitiger
strenger
Java wurde ursprünglich für Online-Systeme im Internet entwickelt.
Aus diesem Grund wurde besonderen Wert auf die Sicherheit gelegt.
Diese Sicherheit ist besonders wichtig, wenn Applets von einem
beliebigen Server auf den eigenen Rechner geladen und ausgeführt
werden. Da Java-Programme keine echten Binärprogramme für ein
bestimmtes Betriebssystem sind, sondern Bytecode für eine virtuelle
Maschine, kann in der Umsetzung des Interpreters für die virtuelle
Maschine einiges an Sicherheit erreicht werden.
11
2 Programmiersprache Java
2.2 Prozesse / Threads in Java
Java ist eine Programmiersprache, die Multithreading unterstützt. Dies bedeutet, daß ein
Prozess (Anwendung) in verschiedene Teile aufgeteilt werden kann. Diese Teilaufgaben
können so von verschiedenen Threads "parallel" abgearbeitet werden. Inhaltlich sind
einzelne Threads eine in sich sequentielle Abfolge von Anweisungen. Als Ganzes
gesehen laufen sie jedoch "parallel" zu andern Threads.
Das Programmieren von Threads ist z.B. sinnvoll bei der Arbeit mit Applets, da es so
möglich ist mehrere Aufgaben unabhängig voneinander ablaufen zu lassen. Ein Thread
kann sich z.B. um die Aktualisierung der Bildschirmausgabe kümmern während ein
anderer Thread die Kommunikation mit dem Server übernimmt. Zu diesem Zweck bietet
Java Primitiven an um Threads zu steuern und zu synchronisieren.
2.3 Zustände von Threads
unbekannt
new
schlafend
interrupt()
erzeugt
suspend()
sleep()
start()
resume()
suspend()
rechenwillig
resume()
suspendiert
suspend()
notify(),
interrupt()
Scheduler
yield()
ausgeführt
run() zu Ende,
stop()
resume()
blockiert
wait(),
join()
beendet
Seit JDK 1.2 verworfen
Abb 2.1: Zustände eines Threads [MiSi1999]
12
2 Programmiersprache Java
Java Threads können sich wie Threads bzw. Prozesse vom Betriebssystem in
verschiedene Zustände befinden.
erzeugt:
In diesen Zustand gelangen Threads nachdem sie durch den newOperator erzeugt wurden. In diesem Zustand kann man die Priorität
einstellen, das Dämon-Flag setzen oder den Thread einer ThreadGruppe zuordnen.
rechenwillig:
In diesen Zustand gelangt der Thread, wenn er durch den Aufruf der
Methode start() gestartet wird.
ausgeführt:
Der Thread erhält durch den Scheduler der Virtual Machine die CPU und
die Ressourcen, die er benötigt um seine run() Methode auszuführen.
Mit der Methode yield() kann der Thread die CPU freiwillig wieder
freigeben.
beendet:
In diesen Zustand gelangt ein Thread, wenn er seine run() Methode
beendet. Die Methode stop() wurde in der Version 1.2 verworfen, da
der Thread keine Möglichkeit hat eventuelle Synchronisationsmechanismen zurückzusetzen.
schlafend:
Mit der Methode sleep() kann ein Thread seine Ausführung für eine
bestimmte Zeit aussetzen. In dieser Zeit kann ein anderer Thread durch
den Aufruf von interrupt() veranlassen, daß der "schlafende"
Thread wieder in den Zustand rechenwillig wechselt. Ein
interrrupt()
Aufruf
wird
dem
Thread
durch
eine
InterruptedException signalisiert, damit er zwischen der "normalen"
Beendigung der sleep() Methode und einem interrupt() Aufruf
unterscheiden kann. Ein "schlafender" Thread behält alle Monitore, die
er vor dem sleep() Aufruf besaß.
blockiert:
In diesen Zustand gelangt ein Thread, wenn er auf eine Ressource
wartet oder sich mit einem anderen Thread synchronisiert. Diese
Synchronisation kann auf mehrere Arten erfolgen (siehe Synchronisation
von Threads).
suspendiert:
In der Version 1.2 wurden die Aufrufe suspend() und resume() die
bewirken, daß ein Thread in den Zustand suspendiert wechselt
verworfen, da sie stark Deadlock gefährdet sind.
Ein Thread besitzt beim Aufruf dieser beiden Methoden keine
Möglichkeit eventuelle Synchronisationsmechanismen zurückzusetzen.
13
2 Programmiersprache Java
2.4 Priorität und Scheduling
In Java wird die Priorität von Threads in 10 Stufen eingeteilt. Die niedrigste Priorität ist 1
und die höchste Priorität ist 10. Wird ein Thread erzeugt besitzt er die Priorität 5
(Thread.NORM_PRIORITY). Diese Priorität kann mit der Methode setPriority()
geändert werden. Die Methode getPriority() liefert die aktuelle Priorität des Thread.
Das Scheduling erfolgt in Java hierarchisch was bedeutet, daß ein Thread erst dann
ausgeführt wird, wenn alle Threads mit einer höheren Priorität abgearbeitet sind oder sich
nicht im Zustand rechenwillig befinden.
Besitzen zwei Threads die selbe Priorität werden keine Angaben über das Scheduling
gemacht. In diesem Fall besitzt das Scheduling des Betriebssystem Einfluß auf die
Abarbeitung der einzelnen Threads. Es ist daher sinnvoll eine Applikation, die
Multithreading nutzt und auf verschiedenen Plattformen laufen soll, auf den einzelnen
Plattformen zu testen.
2.5 Dämon-Threads
Ein Dämon-Thread ist ein Thread der entweder vom System oder vom Entwickler
gestartet wird um im Hintergrund bestimmte Aufgaben auszuführen. Möchte man ein
Dämon-Thread erzeugen, so muß man lediglich nach der Erzeugung eines Threads aber
vor seinem Start mit dem Aufruf setDaemon(true) das Dämon-Flag des Threads auf
true setzen. Mit dem Aufruf isDaemon() kann festgestellt werden ob ein Thread ein
Dämon Thread ist oder nicht.
Ein Java-Programm wird dann beendet, wenn alle seine "normalen" Threads (auch UserThreads genannt) beendet sind. Dämon-Threads werden hierbei nicht berücksichtigt.
Sobald alle User-Threads beendet sind werden auch die Dämon-Threads beendet.
Bei der Konzeption eigener Dämon-Threads ist zu berücksichtigen, daß das
Programmende für Dämon-Threads asynchron, z.B. mitten in der run() Methode
eintreten kann.
14
2 Programmiersprache Java
2.6 Thread Gruppen
Threads werden in Java einer Thread Gruppe zugeordnet. In einer sogenannten
ThreadGroup können Threads zusammengefasst werden, die von ihrer Zugehörigkeit
oder ihrer Aufgabe nach eine Einheit bilden. Wird ein Thread nicht explizit einer Gruppe
zugeordnet so wird er der Gruppe zugeordnet, welcher der erzeugende Thread angehört.
Ohne besondere Maßnahmen ist dies eine voreingestellte Gruppe, die automatisch von
der Virtual Maschine angelegt wird. Bei Applikationen heißt diese Gruppe "main", bei
Applets legt der Browser für jedes Applet eine eigene Gruppe an, die den Name der
Applet-Klasse trägt. Eine ThreadGroup kann sowohl Threads als auch eine weitere
ThreadGroup enthalten.
„main“
Thread 1
Thread A1
ThreadGroup „A“
Thread A2
Thread 2
ThreadGroup „B“
Thread B1
Thread B2
Abb. 2.2: Hierarchische Struktur der Thread-Groups [MiSi1999]
Wie in Abb. 2.2 zu sehen ist entsteht durch diese Anordnung der Thread Gruppen eine
baumartige Struktur. Diese hierarchische Struktur kann in der Klasse SecurityManager
verwendet werden um Zugriffsrechte zuzuteilen. Damit soll verhindert werden das ein
Threads die Priorität der anderen Threads soweit herabstuft das nur noch er die CPU
zugeteilt bekommt. Diese Sicherung gilt allerdings nur für Applets und nicht für "normale"
Anwendungen. Der Browser legt für jedes Applet eine eigene Thread Gruppe an, die
unter der Hauptgruppe steht, in der die System-Threads laufen. Der SecurityManager
erlaubt einem Thread nur Threads seiner Gruppe sowie auf Threads aller Untergruppen
zuzugreifen. Durch diese Zugriffssteuerung wird verhindert, daß ein Applet den Ablauf von
System-Threads beeinträchtigt oder Threads von anderen Applets manipulieren kann.
Folgende Methoden sind von dieser Zugriffssteuerung betroffen:
• setPriority()
• setName()
• setDaemon()
• stop(), suspend(), resume()
15
2 Programmiersprache Java
2.7 Erzeugung und Steuerung von Threads
Vom objektorientierten Standpunkt ist ein Thread in Java auch ein Objekt. Es gibt in Java
zwei verschiedene Möglichkeiten den Code einer Klasse als Thread ausführen zu lassen.
1. Klasse von Thread ableiten
Man leitet eine Klasse von der Klasse Thread ab und überschreibt die run() Methode.
Um einen Thread zu erzeugen wird der Konstruktor der abgeleiteten Klasse
aufgerufen. Gestartet wird dieser Thread anschließend mit dem Aufruf von start().
Beispiel:
class MeinThread extends Thread {
// abgeleitete Klasse
MeinThread (){} ;
// Konstruktor
public void run {
** Hier steht der Programmcode der als Thread
ausgeführt werden soll**
}
}
class EinThread {
// Main-Methode
MeinThread thread1= new MeinThread(); // thread1 wird erzeugt
thread1.start();
// thread1 wird gestartet
}
2. Das Interface Runnable
Ist eine Klasse bereits von einer andern abgeleitet implementiert man das Interface
Runnable damit der Programmcode, der in der run() Methode steht, als Thread
ausgeführt wird. Das Erzeugen eines Threads erfolgt hierbei in zwei Stufen. Als erstes
wird ein Objekt der Klasse erzeugt. Danach wird dieses Objekt dem Konstruktor
Thread(Runnable) übergeben.
Beispiel:
// Klasse mit Interface Runnable
class MeinThread implements Runnable {
MeinThread() {} ;
// Konstruktor
public void run {
** Programmcode der als Thread ausgeführt werden soll**
}
}
class EinThread {
// Main-Methode
Runnable rthread = new MeinThread(); // Runnable Objekt und
Thread thread1 = new Thread(rthread); // thread1 wird erzeugt
thread1.start();
// thread1 wird gestartet
}
16
2 Programmiersprache Java
Der Zusammenhang zwischen Runnable-Objekt und Thread-Objekt ist ähnlich wie
zwischen einem Auto und seinem Lenker.
• Das Runnable-Objekt entspricht dem Auto.
• Das Thread-Objekt entspricht dem Lenker, der das Auto steuert.
• Jedes Auto muß von einer Person gelenkt werden, und jede Person kann nur ein
Auto lenken.
• Der Konstruktor des Thread-Objektes entspricht der Übergabe des Autoschlüssels
an den Lenker: Damit wird festgelegt welches Auto er lenken soll.
• Die start() Methode entspricht dem Starten mit dem Schlüssel, die run()
Methode entspricht dem Laufen des Motors: Der Lenker dreht den Startschlüssel,
und bewirkt, daß der Motor zu laufen beginnt.
Um einen Thread auf einfache Art zu beenden konnte man bis zur Version 1.1 einfach die
Methode Thread.stop() aufrufen. In der Version 1.2 wurde diese Methode allerdings
verworfen, da sie unter bestimmten Umständen problematisch ist. Die Methode
Thread.stop() beendet den Thread ohne ihm die Möglichkeit zu geben Monitore oder
Ergebnisse, die er besitzt, zurückzugeben. Bei den Beispielprogrammen wurde die
Methode Thread.stop() angewendet wenn die gesamte Anwendung, mit allen
Threads, beendet werden soll, da es in dieser Situation keinen Unterschied macht ob ein
Thread eventuell einen Monitor besitzt oder nicht.
Besteht der Rumpf eines Threads aus einer Schleife und möchte man diesen Thread so
beenden, daß er die Möglichkeit besitzt eventuelle Monitore oder Ergebnisse
zurückzugeben sollte statt dessen folgendes Muster anwendet werden:
• Beim Start eine Referenz auf den Thread ermitteln.
• Zum Anhalten wird diese Referenz auf null gesetzt.
• In der Hauptschleife des Threads wird vor jedem Durchgang überprüft, ob diese
Referenz noch auf den Thread selbst verweist. Wenn die Referenz den Wert null hat
oder bereits auf einen anderen Thread verweist, der in der Zwischenzeit neu erzeugt
wurde, beendet der Thread seine Ausführung durch das Verlassen der Schleife.
Beispiel :
class MeinThread extends Thread {
MeinThread () {};
Thread t ;
// wird automatisch mit null initialisiert
public void start() {
if (t == null ) {
t = new Thread(this) ;
t.start() ;
}
// Wenn kein Thread angelegt ist,
// anlegen und starten des Threads
17
2 Programmiersprache Java
public void stop() {
t = null ;
}
// setzen der Referenz auf null
public void run() {
Thread me = Thread.currentThread();
// Referenz des aktuellen
// Threads zuordnen
while ( me == t ) {
// Referenzen vergleichen
** Programmcode der ausgeführt werden soll **
}
}
}
Java bietet für die Arbeit mit Threads eine große Anzahl von Konstruktoren und
Methoden. Konstruktoren und Methoden, die in dieser Diplomarbeit verwendet bzw.
angesprochen werden, werden hier eingehender erläutert [MiSi1999].
Das Paket java.lang.Thread
Datenelemente:
public final static int MIN_PRIORITY = 1
Die niedrigste Priorität, die ein Thread haben kann.
public final static int NORM_PRIORITY = 5
Die zunächst für alle Threads voreingestellte Priorität.
public final static int MAX_PRIORITY = 10
Die höchste Priorität, die ein Thread haben kann.
Konstruktoren:
public Thread()
Erzeugt einen neuen Thread.
public Thread(Runnable target)
Erzeugt einen neuen Thread für die Runnable-Implementierung target.
Methoden:
currentThread
public static native Thread currentThread()
Liefert einen Verweis auf den momentan aktiven Thread.
getName
public final String getName()
Liefert den Namen des Threads.
getPriority
public final int getPriority()
Liefert die Priorität des Threads.
18
2 Programmiersprache Java
isAlive
public final native boolean isAlive()
Liefert true, wenn der Thread lauffähig ist. Dies ist der Fall, wenn
seine start() Methode, aber noch nicht seine stop() Methode
aufgerufen wurde. Der Rückgabewert false signalisiert, daß der
Thread nicht lauffähig ist. Das ist der Fall, wenn der Thread neu
erzeugt wurde, ohne daß bisher seine start() Methode aufgerufen
wurde, oder wenn die Ausführung seiner run() Methode
ordnungsgemäß beendet oder seine stop() Methode aufgerufen
wurde.
isDaemon
public final boolean isDaemon()
Liefert true, wenn der Thread als Daemon läuft, sonst false.
join
public final void join()
Wartet auf die Beendigung des Threads.
run
public void run()
Diese Methode enthält den eigentlichen Anweisungsteil, der nach der
Aktivierung ausgeführt wird. Hierzu muß sie überschrieben werden,
wenn beim Aufruf des Konstruktors kein Runnable-Objekt übergeben
wurde. Falls dem Konstruktor ein Runnable-Objekt übergeben wurde,
wird dessen run() Methode ausgeführt.
setDaemon
public final void setDaemon(boolean on)
Läßt den Thread als Daemon oder User-Thread ausführen, wenn on
true bzw. false ist.
setPriority
public final void setPriority(int newPriority)
Setzt die Priorität des Threads auf newPriority. newPriority muß im
Bereich von MIN_PRIORITY und MAX_PRIORITY liegen.
sleep
public static native void sleep(long millis)
Bewirkt, daß die Ausführung des Threads millis Millisekunden
ausgesetzt wird.
start
public synchronized native void start()
Startet den Thread durch Aufruf der run-Methode.
stop
public final void stop()
Hält den Thread an. Diese Methode wurde in Version 1.2 verworfen,
da sie Inkonsistenzen verursachen kann, wenn sie bei einem Thread
aufgerufen wird, der gerade eine Zugangsmethode eines Monitors
ausführt.
yield
public static native void yield()
Mit dem Aufruf dieser Methode unterbricht der Thread seine
Ausführung, um anderen Threads mit der gleichen Priorität die
Möglichkeit zur Ausführung zu geben. yield() bewirkt nichts, wenn
momentan keine Threads verfügbar sind, die dieselbe Priorität haben
und rechenwillig sind.
19
2 Programmiersprache Java
2.8 Synchronisation
In Java gibt es, wie in Unix oder Windows NT, die Möglichkeit, Threads zu
synchronisieren, damit sie, obwohl sie normalerweise unabhängig von einander
ausgeführt werden, aufeinander abgestimmt werden können.
2.8.1 Synchronisation durch Priorität
Durch die Priorität eines Threads wird in Java die Reihenfolge festgelegt, in der die
Threads einer Applikation oder eines Applets abgearbeitet werden (siehe Kapitel 2.4).
Eine Synchronisation zwischen den Prozessen findet aber nicht statt. Deshalb darf man
streng genommen hier nicht von einer Synchronisation sprechen sondern nur von der
Bevorzugung eines bestimmten Threads. Um die Priorität eines Threads zu verändern
verwendet man die Methode setPriority(). Die Priorität kann von 1 - 10 eingestellt
werden. Es kann zu Schwierigkeiten kommt, wenn man die Priorität eines
rechenintensieven Threads zu hoch setzt (z.B. auf 10). In so einem Fall werden die
System-Threads eventuell vernachlässigt, so daß das System nicht mehr einwandfrei
läuft. Bei einem Applet kann die Priorität nicht beliebig erhöht werden. Das System
verhindert, das ein Applet durch seine Threads das System blockiert.
Mit der Methode getPriority() erhält man die aktuelle Priorität des Threads.
2.8.2 Synchronisation durch Monitore
Das Konzept des Monitors paßt von der Konstruktion her sehr gut zur objektorientierten
Programmierung. Unter einem Monitor kann man sich ein Objekt vorstellen, das
bestimmte Attribute in sich einschließt, auf die nur über definierte Zugangsmethoden
zugegriffen werden kann. Der Monitor ist besetzt, sobald eine Zugangsmethode
ausgeführt wird. Zu jeder Zugangsmethode verwaltet der Monitor eine Warteschlange, in
die Threads eingereiht werden, die eine solche Methode aufrufen, während der Monitor
besetzt ist.
Durch das Einfügen des Modifier synchronized in der Deklaration der Methode erhält
man so einen Monitor. Sobald eine mit syncronized deklarierte Methode von einem
Thread ausgeführt wird, kann kein anderer Thread eine synchronized
Zugangsmethode von diesem Objekt mehr ausführen.
Das folgende Beispiel soll schemenhaft zeigen, wie ein solcher Monitor angewendet
werden kann. Zwei Threads rufen hierbei mit unterschiedlicher Geschwindigkeit die
Methode put() der Klasse Ausgabe auf. Obwohl diese Methode absichtlich
zeitaufwendig gestaltet wurde wird sie zu Ende geführt obwohl ein anderer Thread sie
zwischenzeitlich ebenfalls aufgerufen hat. (Das ausführliche Beispiel mit der
Bildschirmausgabe befindet sich im 6. Kapitel dieser Diplomarbeit).
20
2 Programmiersprache Java
Beispiel für die Synchronisierung einer Methode:
class Ausgabe {
...
// Übergabe eines Strings und der Nummer des Threads
public synchronized void put (String ausgabe, int nummer) {
...
System.out.println ("Thread Nummmer"+nummer+"beginnt mit der
Methode");
System.out.println (ausgabe);
// Ausgabe des Strings
System.out.println ("Thread Nummmer"+nummer+"beendet die
Methode");
}
}
2.8.3 Synchronisation durch Sperr-Objekte
Benötigen die Threads mehrere Ressourcen gleichzeitig, so kann es schnell zu einem
Deadlock kommen wenn synchronized auf Methoden angewendet wird. Bei einem
Java Programm kann dies geschehen, wenn sich zwei Monitore in den Zugangsmethoden
gegenseitig aufrufen.
In diesem Fall kann es vorkommen, daß ein Thread den Monitor A betritt und ein anderer
den Monitor B. Wenn nun in der Zugangsmethode von A eine Zugangsmethode von B
aufgerufen wird und in B eine von A, so wartet der erste Thread darauf, daß der zweite
den Monitor B verläßt, dieser wartet jedoch darauf, daß der erste den Monitor A verläßt.
Benötigt ein oder mehrere Threads mehrere Ressourcen gleichzeitig kann der Zugriff auf
diese Ressourcen durch das Schlüsselwort synchronized auf eine zweite Art
verwendet werden.
Beispiel für die Synchronisation mit einem Sperr-Objekt:
class SteuerObj {}
class MeineKlasse {
SteuerObj Objekt1 = new SteuerObj() ;
....
public void Methode() {
synchronized(Objekt1) {
// kritischer Bereich
** Programmcode der synchronisiert werden soll **
}
}
Wird synchronized wie im Beispiel gezeigt eingesetzt, erfolgt die Synchronisierung
anhand eines Sperr-Objektes. In diesem Beispiel ist das Sperr-Objekt "Objekt1".
21
2 Programmiersprache Java
Bei dieser Art der Synchronisation kann ein Teil einer Methode nicht synchronisiert und
ein anderer Teil synchronisiert ausgeführt werden. Dies hat den Vorteil, daß der
synchronisierte Teil so kurz wie möglich gehalten werden kann, egal wie lang die
eigentliche Methode ist.
Ein weiterer Vorteil besteht darin, daß wenn es nötig ist diese synchronized Blöcke
geschachtelt werden können.
Beispiel für geschachtelte Sperr-Objekte:
synchronized(Objekt1) {
synchronized(Objekt2) {
synchronized(Objekt3) {
// kritischer Bereich
** Programmcode der synchronisiert werden soll **
}
}
}
Bei einer geschachtelten Synchronisation muß allerdings streng darauf geachtet werden,
daß die Threads diese Schachtelung in der gleichen Reihenfolge durchlaufen, da sonst
ein Deadlock entsteht.
Beispiel wie ein Deadlock entstehen kann:
// Thread 1
synchronized(Objekt1) {
synchronized(Objekt2) {
.....
}
}
// Thread 2
synchronized(Objekt2) {
synchronized(Objekt1) {
.....
}
}
Wenn bei diesem Beispiel Thread 1 das Objekt1 belegt und Thread 2 direkt danach
Objekt2 in seinen Besitz nimmt entsteht ein Deadlock, da beide Threads jeweils das
Sperr-Objekt des anderen Threads benötigen um ihren Programmcode weiter
auszuführen.
22
2 Programmiersprache Java
2.8.4 Synchronisation durch Klassenbezogene-Sperre
Wenn man sichergehen muß, daß ein kritischer Bereich für sämtliche Threads
synchronisiert ist muß dieses Synchronisations Objekt das definitiv einzige Exemplar
seiner Klasse sein. In so einem Fall kann man das Class-Objekt verwenden.
Das Class-Objekt einer Klasse ist einmalig in einer Virtual Machine (insofern ist es auch
gerechtfertigt, von »dem« Class-Objekt zu sprechen). Daher können mit diesem Objekt
alle Threads einer Virtual Machine synchronisiert werden, auch dann, wenn sie
unterschiedliche Exemplare der Monitor-Klassen halten oder wenn von der MonitorKlasse gar keine Exemplare erzeugt werden können, weil sie ausschließlich statische
Zugangsmethoden definieren.
Diese Art der Synchronisation kann z.B. bei der Arbeit mit Applets eingesetzt werden.
Obwohl sie völlig getrennte Programme sind laufen sie in der selben Virtual Machine im
Browser. Es ist dabei unwichtig ob sich die Applets in verschiedenen Dokumenten vom
gleichen Host befinden und in getrennten Fenster angezeigt werden.
In dem folgendem Beispiel synchronisieren sich zwei Applets mit Hilfe der Klasse
StaticMonitor indem die Methoden put() und get() jeweils von einem Applet
aufgerufen werden:
public class StaticMonitor {
static int buffer;
static boolean empty = true;
public synchronized static void put(int data) {
try {
Class classLock = StaticMonitor.class; // Class-Objekt wird
// ermittelt
if (!empty)
// Wenn der Speicher voll ist
classLock.wait();
// wird gewartet.
buffer = data;
empty = false;
// Speicher wird auf leer gesetzt
classLock.notify();
// Ein eventuell wartender Thread
}
// wird benachrichtigt und kann jetzt
catch(InterruptedException e) { }
// weiter arbeiten
}
23
2 Programmiersprache Java
public synchronized static int get() {
try {
Class classLock = StaticMonitor.class;
// Class-Objekt
// wird ermittelt
if (empty)
// Wenn der Speicher leer ist
classLock.wait();
// wird gewartet.
System.out.println( "Abgeholt: "+ buffer );
empty = true;
// Speicher wird auf voll gesetzt.
classLock.notify();
// Ein eventuell wartender Thread
}
// wird benachrichtigt und kann jetzt
catch(InterruptedException e) {} // weiter arbeiten.
return buffer;
}
}
Wie in diesem Beispiel zu sehen ist wird zuerst das Class-Objekt in beiden Methoden
ermittelt. Die Synchronisation findet dann über dieses Objekt mittels wait() und
notify() statt. Dadurch, daß die beiden Zugangsmethoden als static vereinbart sind
wird das klassenbezogene Sperren erreicht. Vom grundsätzlichem Aufbau her stimmen
die Zugangsmethoden mit denen, die bei der Synchronisation mit Ereignissen verwendet
werden, überein.
Methoden für das klassenbezogene Sperren und der Synchronisation mit Ereignissen:
wait
public final void wait()
Wartet auf den Eintritt eines Ereignisses. Diese Methode kann nur aus
synchronisierten Methoden heraus aufgerufen werden.
notify
public final native void notify()
Benachrichtigt einen wartenden Thread von einem Ereignis. Kann nur in
synchronisierten Methoden aufgerufen werden.
24
2 Programmiersprache Java
2.8.5 Synchronisation durch Ereignisse
Bei der Synchronisation mit Ereignissen besitzt der Programmierer eine größere Kontrolle
über die Abläufe im Zusammenhang mit Monitoren. So kann der Programmierer z.B. den
Wert eines Datenelementes vorgeben, der vorhanden sein muß damit ein Thread den
Monitor erhält. Ist dies nicht der Fall muß der Thread warten bis das der richtige Wert
bzw. das Ereignis eintritt. In dem Beispielprogramm zu dieser Synchronisationsart wurde
die Anzahl der Bildschirmausgaben, die jeder Thread ohne Unterbrechung ausführt, als
Ereignis verwendet (vollständiges Programm und Bildschirmausgabe ist in Kapitel 6
abgedruckt).
Beispiel für die Synchronisation mit Ereignissen:
class Ausgabe {
private int anzahl=0;
private int soll=3;
boolean th1 = true;
// Anzahl der Ausgaben
// Variable die angibt, welcher
// Thread den Monitor erhält.
// Synchronisierte Methode, die von Thread 1 aufgerufen wird.
public synchronized void put1 (String ausgabe) {
if (!th1)
// Wenn th1 == false
try {wait();}
// muß Thread 1 warten.
catch(InterruptedException e) {}
this.buffer=ausgabe;
System.out.println(buffer);
// Hier erfolgt die Ausgabe auf
// dem Bildschirm.
anzahl=anzahl+1;
// Anzahl der Ausgaben wird heraufgesetzt.
if (anzahl>=soll)
// Anzahl der Ausgaben wird mit dem
{
// Sollwert verglichen.
anzahl=0;
// Anzahl der Ausgaben wird auf 0 gesetzt.
th1=false;
// Variable für den Monitor wird umgestellt
notify();
// Eventuell wartender Thread erhält die
}
// Benachrichtigung, daß er
}
// jetzt weiter arbeiten kann.
}
25
2 Programmiersprache Java
// Synchronisierte Methode, die von Thread 2 aufgerufen wird.
public synchronized void put2 (String ausgabe) {
if (th1)
// Wenn th1 == true
try {wait();}
// muß Thread 2 warten.
catch(InterruptedException e) {}
this.buffer=ausgabe;
System.out.println(buffer);
// Hier erfolgt die Ausgabe
// auf dem Bildschirm.
anzahl=anzahl+1;
// Anzahl der Ausgaben wird heraufgesetzt
if (anzahl>=soll)
// Anzahl der Ausgaben wird mit dem
// Sollwert verglichen.
{
anzahl=0;
// Anzahl der Ausgaben wird auf 0 gesetzt.
th1=true;
// Variable für den Monitor wird umgestellt
notify();
// Eventuell wartender Thread erhält die
}
// Benachrichtigung, daß er jetzt
}
// weiter arbeiten kann.
}
2.8.6 Synchronisation auf das Ende von anderen Threads
In Java definiert die Klasse Thread die Methode join(). Diese Methode kann verwendet
werden um einen Thread auf das Ende eines anderen Threads zu synchronisieren.
Mit join() können auf einfache Weise Verarbeitungsketten implementiert werden, bei
denen ein Thread auf die Fertigstellung eines Zwischenergebnisses warten muß, welches
wiederum von anderen Threads berechnet wird.
Mit dem Aufruf thread2.join() wartet der aufrufende Thread bis der Thread "thread2"
seine Ausführung beendet hat.
Es gibt bei der Verwendung von join() einige Details, die beachtet werden müssen:
• Die Methode join() kann mehrmals hintereinander aufgerufen werden und bewirkt
so, daß auf mehrere Threads gewartet wird. Die Reihenfolge ist hierbei beliebig. Der
aufrufende Thread wartet solange bis der letzte Thread auf den gewartet werden soll
beendet ist.
• Bei der Implementierung ist darauf zu achten, daß alle Threads auf die gewartet
werden soll bereits gestartet sind bevor die Methode join() von einem anderen
Thread verwendet wird. Der Thread, der auf das Ende von anderen Threads wartet
muß als letztes gestartet werden. Bei der Implementierung von mehreren Threads die
wiederum auf das Ende von mehreren Threads warten kann es daher kompliziert
werden die richtige Reihenfolge beim Starten der Threads einzuhalten.
• Erfolgt z.B. der Aufruf thread2.join() und der Thread "thread2" ist bereits beendet,
so bleibt diese Methode ohne Wirkung und der aufrufende Thread wird fortgesetzt.
26
2 Programmiersprache Java
Beispiel für die Synchronisation auf das Ende eines anderen Threads:
class CountingThread extends Thread {
// Modifizierte start()-Methode. Es wird der
// Thread übergeben, auf den gewartet werden soll.
public void start(Thread thread) {
try{thread.join();}
catch(InterruptedException e) {}
super.start();
}
// Ist der Thread, auf den gewartet wird zu
// Ende wird der wartende Thread gestartet.
public void run(){
** Programmcode, der als Thread ausgeführt werden soll **
}
}
class MeinThread2 extends Thread {
// "Normaler" Thread, auf den
// gewartet werden soll.
public void run() {
** Programmcode, der als Thread ausgeführt werden soll **
}
}
class AufEndeThread {
// Main-Methode
...
CountingThread thread1 = new CountingThread(); // Erzeugen der
MeinThread2 thread2 = new MeinThread2();
// beiden Threads
thread2.start();
thread1.start(thread2);
}
//
//
//
//
//
//
Thread, auf den gewartet
werden soll wird zuerst gestartet.
Thread, der warten soll wird
gestartet. Ihm wird der Thread
"thread2" übergeben auf den er
warten soll.
27
2 Programmiersprache Java
2.9 Kommunikation zwischen Threads mit Piped-Streams
Die Ein- und Ausgabe wird in Java mit Hilfe sogenannter Streams realisiert. Einen Stream
kann man sich als eine Art Pipeline vorstellen (siehe Abb. 2.3). Am "Schreibende" wird
der Stream mit Daten gefüllt und am "Leseende" werden diese Daten in der selben
Reihenfolge wieder herausgeholt. Es können dabei keine Daten verloren gehen, da sie
bis zum Auslesen in der Pipe verweilen. Streams sind in Java immer unidirektional.
Daten
Schreiben
Lesen
Stream
Abb. 2.3: Die Arbeitsweise von Streams [MiSi1999]
Das Paket java.io stellt dem Programmierer verschiedene Klassen zur Verfügung, die auf
dem Streamkonzept basieren.
In dieser Diplomarbeit, in der es um die Verwendung von Threads bzw. Prozessen geht,
werden die Piped-Streams, die für einen Datenaustausch zwischen Threads geeignet
sind, eingehender besprochen. Pipes werden auch in Windows NT und Unix eingesetzt.
Die genaue Arbeitsweise ist dabei vom jeweiligem System abhänig und wird daher
separat beschrieben.
Eine Pipe kann in Java mit Hilfe der Klassen PipedReader und PipedWriter realisiert
werden und so die Kommunikation zwischen zwei Threads ermöglichen.
Mit einer Pipe könne Streams direkt miteinander gekoppelt werden. Diese Kopplung der
Streams kann auf verschiedene Arten erfolgen:
• Direkt bei der Erzeugung eines PipedReader oder PipedWriter Objektes:
• PipedReader p = new PipedReader (myPipedWriter);
• PipedWriter p = new PipedWriter (myPipedReader);
• Die Streams können aber auch ohne Parameter initialisiert werden und später durch
die Methode connect() verbunden werden. Diese Methode ist in beiden Klassen
PipedReader und PipedWriter enthalten.
28
2 Programmiersprache Java
Beispiel für die Kommunikation von zwei Threads über eine Pipe:
class CountingThread extends Thread {
PipedInputStream pipein;
// Konstruktor des Threads
public CountingThread(PipedInputStream pipein) {
this.pipein = pipein;
}
// Stream Objekt zum Lesen
// der Pipe wird übernommen.
public void run() {
while(true)
{
try {ende=pipein.read(lese,0,1);}
catch (IOException e) {}
// Pipe wird ausgelesen
if(ende == -1) break;
// bis -1 zurückgegeben wird.
...
}
try {pipein.close();}
catch(IOException e){}
// Schließt den Stream
}
}
class CountingThreadRunnable implements Runnable {
PipedOutputStream pipeout;
// Konstruktor
public CountingThreadRunnable(PipedOutputStream pipeout){
this.pipeout = pipeout;
}
// Stream Objekt zum Schreiben
// in die Pipe wird übernommen.
public void run(){
...
for(hilf=0;hilf<=10;hilf++)
// 10 mal wird eine Zahl
{
// in die Pipe geschrieben
try {pipeout.write(schreibe,0,1);}
catch(IOException e){}
}
try {pipeout.close();}
// Schließt den Stream
catch(IOException e){}
}
}
29
2 Programmiersprache Java
class Pipe {
// Main-Methode
public static void main(String[] args) {
// Erzeugen der Stream- Objekte zum Schreiben und Lesen.
PipedInputStream pipein = new PipedInputStream();
PipedOutputStream pipeout = new PipedOutputStream();
try {pipeout.connect(pipein);}
catch (IOException e) {}
// Kopplung der Streams
// Erzeugung der Threads mit Übergabe der Stream-Objekte.
Runnable rthread = new CountingThreadRunnable(pipeout);
Thread thread2 = new Thread(rthread);
CountingThread thread1 = new CountingThread(pipein);
thread1.start();
thread2.start();
}
}
// Starten der Threads
Die Methoden bzw. Konstruktoren der beiden Klassen PipedReader und PipedWriter
werden hier genauer erläutert [MiSi1999]:
Die Klasse PipedReader:
Konstruktoren:
public PipedReader()
Erzeugt einen neuen PipedReader, der noch keine Verbindung besitzt. Vor der
Benutzung muß er noch mit einem PipedWriter verbunden werden (entweder mit
connect() oder dem Konstruktor von PipedWriter ).
public PipedReader(PipedWriter src)
Erzeugt einen neuen PipedReader, der mit dem PipedWriter src verbunden ist.
Methoden:
close
public void close()
Schließt den Stream.
connect
public void connect(PipedWriter src)
Verbindet den Stream mit src.
30
2 Programmiersprache Java
read
public int read(char[] b, int off, int len)
Versucht, len Zeichen aus dem Stream zu lesen und speichert sie ab
dem Index off in b. Wenn beim Versuch, das erste Zeichen zu lesen,
das Dateiende bereits erreicht ist, ist der Rückgabewert -1. Ansonsten
wird die Anzahl der tatsächlich gelesenen Bytes zurückgeliefert. Wenn
während des Lesevorgangs das Stream-Ende erreicht wird oder wenn
im darunterliegendem Stream nur weniger als len Bytes gelesen
werden können, ohne zu blockieren, kehrt die Methode zurück und
liefert die Anzahl der bis dahin gelesenen Bytes.
Die Klasse PipedWriter:
Konstruktoren:
public PipedWriter()
Erzeugt einen neuen PipedWriter, der noch keine Verbindung besitzt. Vor der
Benutzung muß er noch mit einem PipedReader verbunden werden (entweder mit
connect() oder dem Konstruktor von PipedReader ).
public PipedWriter(PipedReader sink)
Erzeugt einen neuen PipedWriter, der mit dem PipedReader sink verbunden ist.
Methoden:
close
public void close()
Schließt den Stream. Falls der Stream bereits geschlossen war, bleibt
die Methode ohne Auswirkungen.
connect
public void connect(PipedReader sink)
Verbindet den Stream mit sink.
flush
public void flush()
Bewirkt, daß noch gepufferte Daten in den Stream geschrieben werden.
write
public void write(char[] b, int off, int len)
Schreibt die ersten len-Zeichen ab dem Index off aus dem Array b in
den Stream.
31
3 Betriebssystem Windows NT 4.0
3 Betriebssytem Windows NT 4.0
3.1 Grundlegende Eigenschaften
Windows NT ist seit 1993 in Deutschland erhältlich. Die Version 4.0 wurde 1996
herausgegeben. Der Nachfolger Windows 2000 ist seit Ende 1999 bzw. Anfang 2000
erhältlich.
Windows NT ist ein 32-Bit Betriebssystem mit Multiprocessing und Multithreadingfähigkeit.
Windows NT setzt nicht auf DOS auf, sondern ist ein absolut eigenständiges
Betriebsystem. Programme werden unter Windows NT in getrennten Speicherbereichen
ausgeführt, so daß selbst bei Programmabstürzen das System nicht in Mitleidenschaft
gezogen wird.
Jeder Benutzer hat seine eigene Arbeitsumgebung, die er selbst gestalten kann und die
ihm bei jeder neuen Anmeldung wieder zur Verfügung steht. In einem Windows NT
Netzwerk kann diese Umgebung sogar mit auf andere Rechner wandern. Bei Windows
NT kann im Gegensatz zu z.B. LINUX nur ein Benutzer interaktiv mit dem System
arbeiten und Programme ausführen.
Windows NT ist kein Multiuser-System, auf dem gleichzeitig viele Benutzer Programme
ablaufen lassen können. Gleichwohl können mehrere Benutzer Windows NT als DateiServer oder Web-Server nutzen.
Bei Server-Lösungen im sicherheitsrelevanten Bereichen, die gegen Angriffe aus dem
Internet geschützt werden müssen, kann es sehr sinnvoll sein LINUX statt Windows NT
oder SOLARIS in Betracht zu ziehen. Hier kann der erfahrene Administrator bei
Bekanntwerden eines Fehlers selbst sofort den Code des Betriebssystem ändern. Bei
Windows NT ist man auf den Hotfix von Microsoft angewiesen, der natürlich nicht sofort
verfügbar ist.
3.2 Prozesse / Threads in Windows NT
In Windows NT ist ein Prozess die laufende Instanz einer Anwendung. Er setzt sich aus
Codeabschnitten im Speicher zusammen, die aus Programmen und DLL's geladen
wurden. Jeder Prozess verfügt über seinen eigenen Adreßraum und seine eigenen
Ressourcen, wie z.B. Threads, Dateien und dynamisch reservierten Speicher. Prozesse
selbst führen keinen Code aus; sie stellen den Adressraum dar, in denen sich der Code
befindet. Der Code im Adreßraum eines Prozesses wird durch einen Thread abgearbeitet,
wobei jeder Windows NT Prozess mindestens einen ausführenden Thread besitzt.
32
3 Betriebssystem Windows NT 4.0
Windows NT-Prozesse besitzen verschiedene Merkmale:
• Sie sind als Objekte implementiert, und man greift mittels Objektdienste auf sie zu.
• Sie können mehrere ausführende Threads in ihrem Adreßraum beherbergen.
• Sowohl Prozess- als auch Thread-Objekte besitzen eingebaute
Synchronisationsmechanismen.
• Zwischen Windows NT und einem seiner Prozesse herrscht keine Vater/SohnBeziehung.
Ein Prozess besteht aus mehreren Threads, von denen jeder eine sog. Zeitscheibe vom
Windows NT Kernel zyklisch zugewiesen bekommt, während der er abgearbeitet wird.
Beendet der letzte Thread seine Aufgabe, so terminiert der Prozess. [HaWi1997]
3.3 Zustände von Prozessen / Threads
Prozesse und Threads besitzen feste Momente in ihrem Leben wie Einrichten, Ausführen
von Aktionen und Sterben.
Nachdem ein Thread eingerichtet wurde, durchläuft er (nach[Hamilton97]) die folgenden
Stadien:
• Ready:
Diese Threads sind einsatz- bzw. ablaufbereit und werden einzig vom
Scheduler beachtet.
• Standby:
Das ist der Thread, der als nächstes "am Zug" ist. Dieser Thread läuft
als nächstes, wenn das System nicht die Priorität ändert oder ein
Interrupt auftritt.
• Running:
Dieser Thread wird solange ausgeführt, bis seine Zeitscheibe
abgelaufen ist, er freiwillig in den Wartezustand geht, er unterbrochen
oder terminiert wird.
• Waiting:
Normalerweise geht ein Thread in den Wartezustand, wenn er auf
I/O-Informationen wartet, die er zum Weiterarbeiten benötigt. Sobald
diese Ressource verfügbar ist, geht er in den "Ready"-Zustand über.
• Transition:
Ein ablaufbereiter Thread, dessen Ressourcen nicht verfügbar sind.
• Terminated:
Wird ein Thread terminiert, so wird das Objekt des Threads
manchmal nicht sofort gelöscht; es kann noch länger verfügbar
bleiben, um es zu reinitalisieren und wieder zu verwenden.
33
3 Betriebssystem Windows NT 4.0
3.4 Scheduling
In Windows NT wird die Steuerung von Threads durch den sogenannten Dispetcher
geregelt. Der Dispetcher verwaltet eine Dispetscher Ready Queue, eine Liste in der alle
Threads, die sich im Zustand Ready befinden, ihrer Priorität nach geordnet sind. Jeder
Thread erhält einen bestimmten Anteil der CPU-Zeit auch Zeitscheibe genannt. Besitzen
mehrer Threads die selbe Priorität so erhalten all diese Threads eine gleich großen Anteil
der CPU-Zeit. Threads mit einer geringeren Priorität werden erst berücksichtigt, wenn alle
Threads mit einer höheren Priorität abgearbeitet sind.
Die Prioritäten in Windows NT sind in zwei Hauptgruppen unterteilt Echtzeit-Prioritäten
(Prioritätswert 16-31) und der Bereich der variablen Priorität (Prioritätswert 0-15). Der
einzige Thread mit der Priorität 0 ist der "idle" Thread des Kernel-Thread -Objekts, der auf
Deferred-Process-Ojekte wartet. Die meisten Threads besitzen eine Priorität zwischen
1 und 15.
Um ein "Verhungern" des Systems zu vermeiden , werden die variablen Prioritäten
bestimmter Threads entweder nach oben gesetzt, z.B. +2, um die Performence des
vordersten Tasks zu steigern, oder auch erniedrigt, falls ein Thread Ressourcen zu horten
beginnt. Ein stark CPU-lastiger Prozess / Thread, der zudem eine entsprechend hohe
Priorität besitzt wird, während jeder aktiven Phase um eins herabgestuft. Langsam begibt
er sich wieder auf das Niveau seiner Basispriorität. Dieses Verfahren der dynamischen
Prioritätenanpassung macht Windows NT relativ robust.
34
3 Betriebssystem Windows NT 4.0
3.5 Erzeugung und Steuerung von Prozessen / Threads
In Windows NT 4.0 wird ein Prozess gestartet, sobald eine Anwendung aufgerufen wird.
Dieser Prozess eignet sich Speicher, Systemressourcen und Threads an, die er zur
Ausführung der Anwendung benötigt. Ein Thread ist in Windows NT die kleinste
ausführbare Einheit. Beim Starten eines Prozesses wird automatisch mindestens 1
Thread gestartet. Solange noch ein Thread ausgeführt wird, der mit dem Prozess
verknüpft ist bleibt dieser bestehen. Ein Prozess hat also mindestens einen Thread. Oft
besitzt ein Prozess aber auch mehrere Threads zu unterschiedlichen Zeitpunkten (siehe
Abb. 3.1).
Ein Thread ist aber immer einem bestimmten Prozess zugeordnet und existiert nur
innerhalb dieses Prozesses. Anders als in Unix wird in Windows NT zwischen Prozessen
und Threads unterschieden. Ein Windows-NT-Thread ist für das System weniger
aufwendig als z.B. ein Prozess in Unix. Obwohl in Windows NT Prozesse in vielen Fällen
genau so gehandhabt werden können wie Threads befaßt sich diese Diplomarbeit
hauptsächlich mit den Threads, die innerhalb eines Prozesses (Anwendung) erzeugt und
gesteuert werden, da sie vergleichbar zu den Threads in Java und den “Sohnprozessen“
in Unix sind.
Bei Windows NT wird nicht wie bei Unix eine Kopie des aufrufenden Prozesses erzeugt.
Beim Aufruf der CreatThread() Funktion wird ein Thread kreiert, der dann eine
bestimmte Funktion ausführt.
Time
Process
GUI
Repaginate
Read File
Print
Abb. 3.1: Typischer Windows-NT-Prozess mit seinen Threads [HaWi1997]
35
3 Betriebssystem Windows NT 4.0
Funktionen zum Arbeiten mit Threads unter Windows NT die anhand von beispielhaften
C/C++ Code erläutert werden:
Zum Erzeugen eines Threads ruft man die Funktion CreateThread() auf.
Beispiel CreateThread():
Long WINAPI Print (long Parameter)
{
** Hier wird das Programm des Threads ausgeführt **
}
unsigned long nThreadID ;
HANDLE hThread = CreateThread ( NULL,
0,
(LPTHREAD_START_ROUTINE)Print,
(void*)1,
0,
&nThreadID
);
Parameter:
• Ein Pointer auf die SECURITY_ATTRIBUTES-Struktur, die die Sicherheitsattribute für
den neuen Thread festlegen. Wird hier NULL angegeben weist das Betriebssystem
dem Thread einen Standard-Sicherheits-Deskriptor zu.
• Stackgröße für den Thread. Bei NULL erhält der Thread eine Standardgröße, die
Windows NT bei Bedarf anpasst.
• Die Adresse (Name der Funktion) bei der der Thread seine Ausführung beginnt.
• Ein 32-Bit-Parameter, der beim Starten der Funktion übergeben werden kann (hier z.B.
die frei gewählte Nummer des Threads).
• Ist dieses Flag NULL wird der Thread direkt ausgeführt.
Wird hier CREATE_SUSPENDED eingesetzt wartet der Thread bis er durch
ResumeThread() aufgerufen wird.
• Hier wird die Adresse der 32-Bit-Variablen angegeben, in die diese Funktion die
Thread-ID speichern soll.
Als Rückgabewert dieser Funktion erhält man einen Handle auf den erzeugten Thread.
Bei einem Fehler wird NULL zurückgegeben.
36
3 Betriebssystem Windows NT 4.0
Ein Thread Handle sollte nachdem er nicht mehr benötigt wird durch die Funktion
CloseHandle() geschlossen werden. Das Betriebssystem schließt einen Handle auch
automatisch sobald kein Prozess oder Thread mehr existiert der diesen Handle benötigt.
Beispiel CloseHandle():
CloseHandle ( hThread );
Parameter:
•
Handle der geschlossen werden soll.
Um einen Thread zu beenden gibt es mehrere Methoden.
Der Thread kann von sich aus die Funktion ExitThread() aufrufen, sich selber
beenden und einen Rückgabewert übermitteln.
Beispiel: ExitThread(NO_ERROR);
Die ExitThread() Funktion wird auch automatisch aufgerufen wenn der Thread seinen
Programmcode abgearbeitet hat.
Beispiel für einen automatischen Aufruf von ExitThread() :
long WINAPI Threadfunktion()
{
for (int i=0; i<3; i++)
{
** In dieser Schleife wird
}
return NO_ERROR;
}
// Funktion die der Thread ausführt
3 mal etwas ausgeführt **
// Hier wird der Thread beendet.
// Er übergibt NO_ERROR.
37
3 Betriebssystem Windows NT 4.0
Durch die Funktion TerminateThread() kann der Thread beendet werden. Diese
Methode ist allerdings nicht empfehlenswert, da der Thread hierbei keine Möglichkeit
besitzt seine Ausführung solange fortzusetzen, bis er z.B. einen kritischen Bereich
verlassen hat, können Fehler in der Synchronisation enstehen.
Beispiel TerminateThread():
TerminateThread ( hThread, NO_ERROR );
Parameter:
• Handle für den zu beendenden Thread.
• Exit-Code für diesen Thread.
Den Exit-Code eines Threads kann man mit der Funktion GetExitCodeThread()
erhalten. Wird diese Funktion für einen Thread aufgerufen, der noch nicht beendet ist,
liefert diese Funktion STILL_ACTIV zurück.
Beispiel GetExitCodeThread():
DWORD dwResult;
GetExitCodeThread ( hThread, &dwResult );
If ( dwResult != NO_ERROR )
{
** Dieser Programmteil wird im Falle eines Fehler ausgeführt **
}
Parameter:
• Handle für den zu befragenden Thread.
• Angabe der Adresse einer 32-Bit-Variable, in die der Exit-Code gespeichert werden
soll.
38
3 Betriebssystem Windows NT 4.0
Ein weiterer wichtiger Punkt mit der Arbeit von Threads und Prozessen in Windows NT ist
die jeweilige Priorität. In Windows NT gilt je höher der Prioritätswert, desto “dringender“ ist
der Prozess bzw. Thread. Diese Priorität kann vom Benutzer verändert werden. Das
Betriebssystem hat allerdings auch Einfluß auf die Priorität. In Windows NT besitzt der
Programmierer aber einen viel stärkeren Einfluß auf die Systemabhängige automatische
Steuerung der Prozesse und Threads als in Unix. Es ist daher darauf zu achten, das man
die Priorität einzelner Prozesse und Threads nicht zu stark anhebt, da ansonsten das
System nicht mehr optimal arbeiten kann.
Damit dies nicht geschieht gibt es in Windows NT verschiedene Klassen von Prioritäten
für Prozesse (siehe Abb. 3.2).
•
•
•
•
IDLE_PRIORITY_CLASS
NORMAL_PRIORITY_CLASS
HIGH_PRIORITY_CLASS
REALTIME_PRIORITY_CLASS
31
30
29
28
27
26
25
24
REALTIME_PRIORITY_CLASS
23
22
21
20
19
18
17
16
15
14
13
HIGH_PRIORITY_CLASS
12
11
10
9
NORMAL_PRIORITY_CLASS
(Foreground)
NORMAL_PRIORITY_CLASS
(Background)
8
7
6
5
4
IDLE_PRIORITY_CLASS
3
2
1
Abb. 3.2: Proiritätsstufen in Windows NT [HaWi1997]
Die Priorität eines Threads kann mit der Funktion SetThreadPriority() verändert
werden. Dies gilt allerdings nur für die relative Priorität des Threads. Die Prioritätsklasse
ist abhängig davon welche Klasse der Prozess besitzt, der den Thread gestartet hat. Ein
Prozess kann nicht einen Thread in der Prioritätsklasse IDLE_PRIORITY_CLASS
besitzen und einen anderen in der REALTIME_PRIORITY_CLASS.
39
3 Betriebssystem Windows NT 4.0
Beispiel SetThreadPriority():
SetThreadPriority ( hThread,
THREAD_PRIORITY_LOWEST
);
Parameter:
• Handle für den Thread, dessen Priorität geändert werden soll.
• Prioritätsklasse des Threads innerhalb der Prozess-Prioritätsklasse.
• THREAD_PRIORITY_LOWEST
2 Stufen unter der Prioritätsklasse
des Prozesses.
• THREAD_PRIORITY_BELOW_NORMAL
1 Stufe unter der Prioritätsklasse des
Prozesses.
• THREAD_PRIORITY_NORMAL
Prioritätsklasse des Prozesses
• THREAD_PRIORITY_ABOVE_NORMAL
1 Stufe über der Prioritätsklasse des
Prozesses.
• THREAD_PRIORITY_HIGHEST
2 Stufen über der Prioritätsklasse
des Prozesses.
Es gibt zusätzlich zu diesen Möglichkeiten noch 2 weitere Parameter für die Benutzung
mit der SetThreadPriority() Funktion:
• THREAD_PRIORITY_IDLE setzt die Priorität des Threads auf 1, es sei denn, die
Prioritätsklasse des Prozesses ist die REALTIME_PRIORITY_CLASS in diesem Fall
wird die Priorität des Threads auf 16 gesetzt.
• THREAD_PRIORITY_TIME_CRITICAL setzt die Priorität des Threads auf 15, es sei
denn, die Prioritätsklasse des Prozesses ist die REALTIME_PRIORITY_CLASS in
diesem Fall wird die Priorität des Threads auf 31 gesetzt. Dies ist die höchste Priorität
und auch der einzige Weg einen Thread diese extrem hohe Priorität zuzuteilen.
Um die Priorität eines laufenden Threads abzufragen benutzt man die Funktion
GetThreadPriority().
Beispiel GetThreadPriority():
int prio;
prio = GetThreadPriority ( hThread );
Parameter:
•
Handle des Thread dessen Priorität abgefragt wird.
Als Rückgabewert wird die Priorität in die Variable prio geschrieben.
40
3 Betriebssystem Windows NT 4.0
3.6 Synchronisation von Threads
In Windows NT gibt es wie unter anderen Betriebssystemen auch verschiedene Methoden
um Threads zu synchronisieren.
3.6.1 Synchronisation durch Priorität
Durch das verändern der Priorität eines Threads kann man festlegen welcher Thread
innerhalb eines Prozesses die meiste Rechenzeit zur Verfügung gestellt bekommt. Eine
Synchronisation zwischen den Prozessen findet aber nicht statt. Deshalb darf man streng
genommen hier nicht von einer Synchronisation sprechen sondern nur von der
Bevorzugung eines bestimmten Threads. Die Funktionen, SetThreadPriority() und
GetThreadPriority(), die hierfür benötigt werden sind im Kapitel 3.5 genauer
beschrieben.
3.6.2 Synchronisation durch kritische Bereiche
Mit Hilfe von kritischen Bereichen erreicht man einen wechselseitigen Ausschluß von
Threads. So das bestimmte Programmabschnitte immer nur von einem Thread ausgeführt
werden können. Ein oder mehrere andere Threads die den kritischen Bereich betreten
wollen müssen warten bis der Thread innerhalb des kritischen Bereiches diesen wieder
verläßt (siehe Abb.3.3).
Critical Section
Abb. 3.3: Ausführung eines kritischen Bereiches von einem Thread [HaWi1997]
Der kritische Bereich wird dabei durch eine Variable überwacht. Diese Variable wird
global initialisiert, damit jeder Thread auf diese Variable zugreifen kann.
41
3 Betriebssystem Windows NT 4.0
Beispiel kritischer Bereich:
CRITICAL_SECTION csOutput ; // Initialisierung der Variable um
int zahl = 10;
// den kritischen Bereiches zu
// überwachen.
void Print ()
{
_try
{
// Hier versucht der Thread in den kritischen
// Bereich zu gelangen indem er Zugriff auf
EnterCriticalSection ( &csOutput );
// die Variable erhält.
zahl -- ;
cout << “Die Zahl ist “ << zahl << endl; // Innerhalb des
// Bereiches wird die Variable zahl
}
// herabgezählt und ausgegeben.
_finally
{
LeaveCriticalSection ( &csOutput ) ; // Am Ende des kritischen
}
// Bereiches wird die
}
// Variable wieder “freigegeben“.
In diesem Beispiel hat der Einsatz des kritischen Bereiches den Vorteil, daß der selbe
Thread, der die Variable “zahl“ herabzählt diese auch ausgibt ohne das ein anderer
Thread die Möglichkeit besitzt die Variable vor der Ausgabe zusätzlich herabzuzählen.
Kritische Bereiche werden in den Beispielprogrammen Semaphor und Philosophen
eingesetzt (siehe Kapitel 6.2).
42
3 Betriebssystem Windows NT 4.0
3.6.3 Synchronisation durch Ereignisse
In Windows NT ist ein Ereignis ein Synchronisationsobjekt, das von dem Betriebssystem
verwaltet wird. Diesen Synchronisationsobjekten können Namen gegeben werden so das
nicht nur Threads innerhalb eines Prozesses mit ihnen arbeiten können sondern auch
mehrere Prozesse sich durch so ein Objekt synchronisieren können. Diese
Synchronisationsart wird z.B. verwendet wenn ein Thread auf ein Ergebnis warten muß,
daß von einem anderen Thread zuerst berechnet wird. Die Abbildung 3.4 zeigt einen
allgemeinen Ablauf bei der Synchronisation durch Ereignisse.
Thread A
Thread B
WaitForSingleObject( hEvent )
Thread A is blocked
Thread A resumes
SetEvent( hEvent )
Abb. 3.4: Grundprizip für die Verwendung von Ereignissen [HaWi1997]
Ein Ereignis kann in Windows NT zwei Zustände besitzen.
•
signalisierend
Dieser Zustand bedeutet, daß ein Thread, der eine Anfrage
stellt eine Antwort erhält und so der anfragende Thread sein
Programm weiter ausführen kann.
•
nicht signalisierend
Dieser Zustand bedeutet, daß ein Thread, der eine Anfrage
stellt keine Antwort erhält und so der anfragende Thread
blockiert bis das das Ereignis in den Zustand signalisierend
wechselt.
43
3 Betriebssystem Windows NT 4.0
Bei der Synchronisation mit Hilfe von Ereignissen muß man als erstes einen sogenannten
Ereignis-Handler erstellen. Dies geschieht durch die Funktion CreateEvent().
Beispiel CreateEvent():
HANDLE hEvent = CreateEvent ( NULL,
FALSE,
FALSE;
"EventName"
);
Parameter:
• Ein Pointer auf SECURITY_ATTRIBUTES-Struktur. NULL kann eingesetzt werden wenn
keine Sicherheitsaspekte berücksichtigt werden müssen.
• Dieses Flag wird normalerweise auf FALSE gesetzt. Es gibt an ob es sich um ein
Manueller-Reset-Ereignis handelt.
• Diese Flag gibt den Anfangsstatus des Ereignisses an TRUE = signalisierend
FALSE = nicht signalisierend.
• Hier kann man dem Ereignis einen Namen geben damit auch andere Prozesse mit
diesem Ereignis arbeiten können. Man muß allerdings beachten, daß der verwendete
Name nicht bereits vergeben ist. Ereignisse, Mutex und Datei-Mapping-Objekte
verwenden dabei den selben Namensbereich. Ist dieser Wert NULL so kann das
Ereignis nur innerhalb des Prozesses verwendet werden.
Als Rückgabewert dieser Funktion erhält man einen Handle auf das Ereignis oder NULL
falls ein Fehler aufgetreten ist.
44
3 Betriebssystem Windows NT 4.0
Ein anderer Prozess kann einen Handle zu dem selben Ereignis erhalten, indem er die
CreateEvent() Funktion mit den selben Parameter aufruft oder die OpenEvent()
Funktion benutzt.
Beispiel OpenEvent():
HANDLE hEvent = OpenEvent ( EVENT_ALL_ACCESS,
FALSE,
“EventName“
);
Parameter:
• Sicherheitsparameter der die Zugriffsrechte angibt.
• EVENT_ALL_ACCESS
gibt alle Zugriffsrechte frei.
• EVENT_MODIFY_STATE
gibt die Funktion SetEvent() und ResetEvent()
frei.
• SYNCHRONIZE
gibt nur den Warte-Aufruf frei.
• TRUE gibt an, das ein Prozess der durch CreateProzess() kreiert wird diesen
Handle erben kann. Bei FALSE ist dies nicht der Fall.
• Name des Ereignisses
Als Rückgabewert dieser Funktion erhält man einen Handle auf das Ereignis oder NULL
falls ein Fehler aufgetreten ist.
Event-Handle sollte man, wenn sie nicht mehr benötigt werden, schließen. Dies geschieht
mit der Funktion CloseHandle() (siehe Kapitel 3.5).
Um ein Ereignis-Handle in den Zustand signalisierend zu setzen benötigt man die
Funktion SetEvent().
Beispiel SetEvent():
SetEvent ( hEvent );
Parameter:
•
Handle des Events welches auf signalisierend gesetzt werden soll.
45
3 Betriebssystem Windows NT 4.0
Da es zwei Arten von Ereignissen gibt und zwar das Auto-Reset-Ereignis und das
Manueller-Reset-Ereignis gibt es Unterschiede im Zurücksetzen dieser beiden
Ereignisarten.
Beim Auto-Reset-Ereignis folgt der Übergang in den nicht signalisierend Zustand
automatisch nachdem eine Warte-Anfrage eines anderen Threads beantwortet wurde.
Beim Manueller-Reset-Ereignis muß das Ereignis durch die Funktion ResetEvent()
manuell zurückgesetzt werden.
Beispiel ResetEvent():
ResetEvent ( hEvent );
Parameter:
•
Handle des Events welches auf nicht signalisierend gesetzt werden soll.
Eine Besonderheit ist die PulseEvent() Funktion. Sie wird eingesetzt, wenn man
mehrere Threads auf ein bestimmtes Ereignis warten lassen will. Da hierbei das AutoReset-Ereignis nach jeder Warte-Anfrage neu auf signalisierend gesetzt werden muß
und es bei dem Manueller-Reset-Ereignis schwierig ist zu bestimmen ob nun wirklich alle
wartenden Threads eine Antwort erhalten haben wird in so einer Situation die
PulseEvent() Funktion verwendet.
Bei dieser Funktion wird das Ereignis solange auf signalisierend gesetzt, bis alle
wartenden Threads eine Antwort erhalten haben.
Beispiel PulseEvent():
PulseEvent ( hEvent );
Parameter:
• Handle des Events welches angesprochen werden soll.
46
3 Betriebssystem Windows NT 4.0
Es gibt zwei verschiedene Funktionen für eine Warte-Anfrage abhängig davon ob nur auf
ein oder auf mehrere Ereignisse gewartet werden soll.
Bei der Funktion WaitForSingleObject() wird auf das Signal eines Ereignisses
gewartet.
Bei der Funktion WaitForMultipleObjects() wird auf das Signal von mehreren
Ereignissen gewartet.
Im ersten Moment könnte man annehmen das man anstatt zwei verschiedene Funktionen
zu verwenden einfach die WaitForSingleObject() Funktion mehrmals hintereinander
aufrufen kann. Bei so einem Vorgehen besteht aber die große Gefahr eines Deadlocks,
da sich so mehrere Threads gegenseitig sperren können (siehe Abb. 3.5).
Bei der Verwendung der WaitForMultipleObjects() Funktion werden alle benötigte
Ereignisse zur selben Zeit überwacht und entweder alle oder keines der Signale
verwendet.
Thread A
Thread B
Wait ForSingleObject( hMutexZ );
Wait ForSingleObject( hMutexY );
// Thread A gets control of Handle hMutexZ
// waits for hMutexY
// Thread B gets control of Handle hMutexY
// waits for hMutexZ
Wait ForSingleObject( hMutexY );
Wait ForSingleObject( hMutexZ );
Abb. 3.5: Deadlock-Situation durch geschachtelte WaitForSingleObject() [HaWi1997]
Beispiel WaitForSingleObject():
DWORD dwResult = WaitForSingleObject ( hEvent,
INFINITE
);
Parameter:
• Handle des Events, Mutex oder Semaphors welches angesprochen werden soll.
• Die Zeit in Millisekunden die gewartet werden soll. INFINITE bedeutet keine
Zeitbeschränkung.
47
3 Betriebssystem Windows NT 4.0
Es gibt mehrere mögliche Rückgabewerte dieser Funktion:
• WAIT_OBJECT_0
wenn der Handle antwortet.
• WAIT_TIMEOUT
wenn die vorgegebene Zeit abgelaufen ist.
• WAIT_ABANDONED wenn auf einen Mutex-Handle gewartet wird.
Beispiel WaitForMultipleObjects():
HANDLE hEvents[2]
DWORD dwResult = WaitForSingleObject ( 2,
hEvents,
TRUE,
INFINITE
);
Parameter:
• Die Anzahl der Handles, die überprüft werden sollen.
• Adresse auf das Handle-Array.
• Flag, das angibt, ob alle Handles signalisieren müssen oder nur eins. TRUE bedeutet,
das alle Ereignis-Handles signalisieren müssen. Bei der Verwendung von FALSE muß
darauf geachtet werden das kein Deadlock entsteht (siehe oben).
• Die Zeit in Millisekunden die gewartet werden soll. INFINITE bedeutet keine
Zeitbeschränkung.
Die Rückgabewerte dieser Funktion unterscheiden
WaitForSingleObject() Funktion:
sich
etwas
gegenüber
der
• WAIT_OBJECT_0 ersetzt durch WAIT_OBJECT_0 + ( Anzahl der Handles – 1 ) wird
zurückgegeben, wenn der Handle das Signal gegeben hat. Der Rückgabewert
bestimmt den Index des mit der niedrigsten Nummer versehenen Handles, der ein
Signal bekommen soll.
• WAIT_TIMEOUT wenn die vorgegebene Zeit abgelaufen ist und nicht alle Handles
signalisiert haben.
• WAIT_ABANDONED_0 ersetzt durch WAIT_ABANDONED_0 + ( Nummer des Handles -1)
wird nur zurückgegeben, wenn ein Handle auf einen Mutex-Handle wartet.
48
3 Betriebssystem Windows NT 4.0
3.6.4 Synchronisation durch Mutex
Ein Mutex ist ein Windows-NT-Objekt, das für den wechselseitigen Ausschluß verwendet
wird. Mutex ist sozusagen eine Mischung aus "kritische Bereiche" und "Ereignisse". Bei
einem
Mutex
sind
die
Warte-Aufrufe
WaitForSingleObject()
und
WaitForMultipleObjects() der Threads die selben wie bei Ereignissen. Andere
Funktionen für die Arbeit mit Mutex-Objekten ähneln sehr der Funktionen die bei der
Arbeit mit Ereignissen verwendet werden.
Ein Mutex-Objekt wird mit der Funktion CreateMutex() kreiert.
Beispiel CreateMutex():
HANDLE hMutex = CreateMutex ( NULL,
TRUE;
"MutexName"
);
Parameter:
• Ein Pointer auf die SECURITY_ATTRIBUTES-Struktur. NULL kann eingesetzt werden
wenn keine Sicherheitsaspekte berücksichtigt werden müssen.
• Diese Flag gibt den Anfangsstatus des Mutex an TRUE = kritischer Bereich frei.
• Hier kann ein Name angegeben werden um auch mit anderen Prozessen dieses Mutex
zu verwenden. Man muß allerdings beachten, daß der verwendete Name nicht bereits
vergeben ist. Ereignisse, Mutex und Datei-Mapping-Objekte verwenden dabei den
selben Namensbereich. Ist dieser Wert NULL so kann das Ereignis nur innerhalb des
Prozesses verwendet werden.
Als Rückgabewert dieser Funktion erhält man einen Handle auf das Mutex oder NULL falls
ein Fehler aufgetreten ist.
49
3 Betriebssystem Windows NT 4.0
Möchte nun ein Thread in einen kritischen Bereich eintreten ruft er die Funktion
WaitForSingleObject() oder WaitForMultipleObjects() auf. Ist der kritische
Bereich frei so wird der Programmteil ausgeführt. Am Ende des kritischen Bereiches muß
der Thread den Mutex wieder "frei" geben. Dies geschieht mit der Funktion
RelaseMutex().
Wird der Zugriff eines kritischen Bereiches durch mehrere Mutexe gesteuert muß die
Funktion RelaseMutex() sooft aufgerufen werden, bis alle Mutexe wieder freigegeben
sind.
Beispiel RelaseMutex():
RelaseMutex ( hMutex);
Parameter:
• Handle des Mutex welches wieder freigegeben werden soll.
Wie bei Ereignissen können auch Mutexe, wenn sie einen Namen haben, von anderen
Prozessen benutzt werden. Dies geschieht durch die Funktion OpenMutex().
Beispiel OpenMutex():
HANDLE hMutex = OpenMutex ( MUTEX_ALL_ACCESS,
FALSE,
“MutexName“
);
Parameter:
• Sicherheitsparameter der die Zugriffsrechte angibt. MUTEX_ALL_ACCESS gibt alle
Zugriffsrechte frei.
• TRUE gibt an, das ein Prozess der durch CreateProzess() kreiert wird diesen
Handle erben kann. Bei FALSE ist dies nicht der Fall.
• Name des Ereignisses
Mutex-Handel sollte man, wenn sie nicht mehr benötigt werden, schließen. Dies geschieht
mit der Funktion CloseHandle() (siehe Kapitel 3.5).
Die Anwendung eines Mutex wird im Beispielprogramm Philosophen (Kapitel 6.2.7)
gezeigt.
50
3 Betriebssystem Windows NT 4.0
3.6.5 Synchronisation durch Semaphore
Semaphore arbeiten in Windows NT nach dem selben Prinzip wie in Unix und Java. Es
sind sozusagen Zähler dessen Werte erhöht oder herabgezählt werden je nachdem
welche Operation auf so einen Semaphor einwirkt. Ist der interne Zähler NULL muß ein
Thread warten, wenn er mit seiner Operation den internen Zähler herabsetzen will. (Ein
kleines Beispiel für die Arbeitsweise eines Semaphores ist im Kapitel 4.6.1 beschrieben.)
Als erstes muß ein Semaphor mit der Funktion CreateSemaphor() erzeugt werden
damit später die Threads mit ihm arbeiten können. In Windows NT wird der Zugang zu
einem kritischen Bereich, auch bei der Verwendung eines Semaphores zur
Synchronisation, durch die schon oben erläuterte WaitForSingleObject() oder bei
der Verwendung von mehreren Semaphoren durch die WaitForMultipleObjects()
Funktion erreicht. Wie bei der Synchronisation durch Mutex muß hier auch beim
Verlassen des kritischen Bereiches ein Funktionsaufruf erfolgen, der in diesem Fall den
internen Zähler des Semaphores wieder erhöht. Diese Funktion lautet
ReleaseSemaphore(). Wie bei den anderen Synchronisationsarten sollte auch hier der
Handle des Semaphores durch die Funktion CloseHandle() (siehe Kapitel 3.5)
geschlossen werden.
Damit andere Prozesse auf ein durch CreateSemaphor() erstelltes Semaphor
zugreifen können benötigt man auch hier eine “Open“ Funktion und zwar die
OpenSemaphore() Funktion.
Beispiel CreateSemaphor():
HANDLE semaphor = CreateSemaphore ( NULL,
3,
3,
“SemapName“
);
Parameter:
• Ein Pointer auf SECURITY_ATTRIBUTES-Struktur. NULL kann eingesetzt werden wenn
keine Sicherheitsaspekte berücksichtigt werden müssen.
• Der Anfangswert, den das Semaphor besitzen soll. Dieser Wert muß größer oder
gleich NULL sein.
• Der Maximalwert, den das Semaphor annehmen darf. Dieser Wert muß gleich oder
größer sein als der angegebene Anfangswert. Mindestwert ist hier 1.
• Hier kann man dem Semaphor einen Namen geben damit auch andere Prozesse mit
diesem Semaphor arbeiten können. Man muß allerdings beachten, daß der
verwendete Name nicht bereits vergeben ist. Ereignisse, Mutex und Datei-MappingObjekte verwenden dabei den selben Namensbereich. Ist dieser Wert NULL so kann
das Ereignis nur innerhalb des Prozesses verwendet werden.
Als Rückgabewert dieser Funktion erhält man einen Handle auf das Semaphor oder NULL
falls ein Fehler aufgetreten ist.
51
3 Betriebssystem Windows NT 4.0
Beispiel OpenSemaphore():
HANDLE semaphor = OpenSemaphore ( SEMAPHORE_ALL_ACCESS,
NULL,
“SemapName“
);
Parameter:
• Sicherheitsparameter der die Zugriffsrechte angibt.
• SEMAPHORE_ALL_ACCESS
gibt alle Zugriffsrechte frei.
• SEMAPHORE_MODIFY_STATE
gibt die Funktion ReleaseSemaphore() frei.
• SYNCHRONIZE
gibt nur den Warte-Aufruf frei.
• TRUE gibt an, das ein Prozess der durch CreateProzess() kreiert wird diesen
Handle erben kann. Bei FALSE ist dies nicht der Fall.
• Name des Semaphores.
Als Rückgabewert dieser Funktion erhält man einen Handle auf das Semaphor oder NULL
falls ein Fehler aufgetreten ist.
Beispiel ReleaseSemaphore():
ReleaseSemaphore ( semaphor,
1,
NULL
);
Parameter:
• Handle des Semaphores.
• Wert, um den das Semaphor erhöht werden soll.
• Hier kann man wenn es benötigt wird einen Pointer auf eine long-Variable angeben
damit man den vorherigen Wert des internen Zählers erhält. Wird der vorherige Wert
nicht benötigt kann hier NULL angegeben werden.
Diese Funktion gibt als Rückgabewert TRUE zurück wenn kein Fehler auftritt ansonsten
wird FALSE zurückgegeben.
52
3 Betriebssystem Windows NT 4.0
3.7 Kommunikation
In Windows NT gibt es, wie bei den anderen Umgebungen mehrere Arten der
Kommunikation. Die Kommunikation zwischen Prozessen bzw. Threads findet mit der
Hilfe von Pipes statt. Daher wird diese Art der Kommunikation hier näher erläutert. Eine
Pipe ist dabei ein Kommunikationskanal wodurch die Prozesse bzw. Threads miteinander
verbunden sind.
In Windows NT gibt es zwei Grundtypen von Pipes unbenannte Pipes (Kapitel 3.7.1) und
benannte Pipes (Kapitel 3.7.2). Beide Pipetypen benutzen zur Kommunikation die
standardmäßigen Dateiverwaltungsfunktionen von Windows NT. ReadFile() zum
Auslesen der Pipe und WriteFile() zum Schreiben in die Pipe.
Die Funktionen ReadFileEx() und WriteFileEx() die ausschließlich für asynchronen
Datei-In bzw. Output verwendet werden können auch für Pipes verwendet werden. In
dieser Diplomarbeit werden diese beiden Funktionen nicht näher erläutert.
Beispiel ReadFile():
BOOL fRead = ReadFile ( hRead,
szBuffer,
sizeof(szBuffer),
&dwRead,
NULL
);
if ( fRead == FALSE || dwRead == 0 )
{
// Fehlermeldung
}
Parameter:
• Datei-Handler, von dem gelesen werden soll.
• Adresse des Puffers, der die Daten aufnehmen soll.
• Die maximale Anzahl der auszulesenden Bytes.
• Ein Pointer auf ein DWORD. Dort wird die Anzahl der tatsächlich ausgelesenen Bytes
gespeichert.
• Optionale OVERLAPPED-Struktur für asynchrones lesen. NULL wenn diese Option nicht
genutzt werden soll.
Dieser Parameter wird bei einer unbenannten Pipe von Windows NT ignoriert.
Diese Funktion gibt TRUE zurück falls kein Fehler aufgetreten ist ansonsten FALSE.
53
3 Betriebssystem Windows NT 4.0
Beispiel WriteFile():
BOOL fWritten = WriteFile ( hWrite,
szBuffer,
sizeof(szBuffer),
&dwWritten,
NULL
);
if ( fWritten == FALSE || dwWritten == 0 )
{
// Fehlermeldung
}
Parameter:
• Datei-Handler, der beschrieben werden soll.
• Adresse des Puffers, der die zu schreibenden Daten enthält.
• Die maximale Anzahl der zu schreibenden Bytes.
• Ein Pointer auf ein DWORD. Dort wird die Anzahl der tatsächlich geschriebenen Bytes
gespeichert.
• Optionale OVERLAPPED-Struktur für asynchrones lesen. NULL wenn diese Option nicht
genutzt werden soll.
Dieser Parameter wird bei einer unbenannten Pipe von Windows NT ignoriert.
Diese Funktion gibt TRUE zurück falls kein Fehler aufgetreten ist ansonsten FALSE.
54
3 Betriebssystem Windows NT 4.0
3.7.1 Unbenannte Pipes
Dieser Type von Pipe wird zur Kommunikation zwischen zusammenhängenden
Prozessen auf einem Rechner eingesetzt wie z.B. zwischen Mutter- und Kindprozess.
Sie können nicht für die Kommunikation in Netzwerken verwendet werden. Eine
unbenannte Pipe arbeitet unidirektional, so daß es immer ein “Leseende“ und ein
“Schreibende“ gibt. Möchte man zwischen einem Mutter- und einem Kind-Prozess in
beide Richtungen kommunizieren, so benötigt man zwei unbenannte Pipes.
Eine unbenannte Pipe wird mit der Funktion CreatePipe() erzeugt. Eine Pipe wird
solange nicht geschlossen, bis alle offene Handles, auch die geerbten Handles,
geschlossen sind. Mit der Funktion CloseHandle() (siehe Kapitel 3.5) werden die
einzelnen Handles der Pipe geschlossen.
Beispiel CreatePipe():
BOOL fCreated = CreatePipe ( &hRead,
&hWrite,
NULL,
0
);
if ( !fCreated )
// Fehlermeldung
Parameter:
• Die Adresse der Variable, die den Read-Handle speichern soll.
• Die Adresse der Variable, die den Write-Handle speichern soll.
• Die Adresse einer SECURITY_ATTRIBUTES-Struktur oder NULL für den StandardSicherheitsdeskriptor.
• Die Puffergröße der Pipe oder 0 für die Standardgröße.
Diese Funktion gibt TRUE zurück falls kein Fehler aufgetreten ist ansonsten FALSE.
Soll ein Kind-Prozess die Handles einer unbenannten Pipe erben so benötigt man eine
SECURITY_ATTRIBUTES-Struktur, da der Standard Sicherheitsdeskriptor eine Vererbung
der Pipe-Handles nicht zuläßt.
Beispiel für die Verwendung einer SECURITY_ATTRIBUTES-Struktur damit die PipeHandles vererbt werden können:
SECURITY ATTRIBUTES sec;
sec.nLength = sizeof( SECURITY ATTRIBUTES );
sec.lpSecurityDescriptor = NULL;
sec.bInheritHandle = TRUE;
CreatePipe ( &hRead, &hWrite, &sec, 0 );
55
3 Betriebssystem Windows NT 4.0
3.7.2 Benannte Pipes
Benannte Pipes besitzen einige Vorzüge gegenüber unbenannte Pipes. Benannte Pipes
können nicht nur durch Handles sondern auch durch einen Namen angesprochen werden.
Sie können im Gegensatz zu unbenannten Pipes auch zur Kommunikation innerhalb
eines Netzwerkes verwendet werden. Eine benannte Pipe kann zur Kommunikation in
zwei Richtungen eingesetzt werden. Ihre Verbindungsoptionen sind flexibler als die von
unbenannten Pipes.
Eine benannte Pipe unterstützt zudem asynchrones, überlappendes I/O welches bei einer
unbenannten Pipe von Windows NT ignoriert wird selbst wenn der dafür erforderliche
Parameter angegeben wird. Wie eine unbenannte Pipe wird eine benannte Pipe solange
nicht geschlossen, bis alle offene Handles geschlossen sind. Mit der Funktion
CloseHandle() (siehe Kapitel 3.5) werden die einzelnen Handles der Pipe geschlossen.
Der Funktionsaufruf einer benannten Pipe ist CreateNamedPipe(). Damit ein anderer
Prozess oder Thread mit dieser Pipe arbeiten kann muß er die Funktion CreateFile()
aufrufen damit er einen Handle für die Pipe erhält.
Wird eine Pipe von anderen Prozessen benutzt muß der Server-Prozess die Pipe durch
ConnectNamedPipe() in den "Empfangen-Modus" versetzen bevor eine andere
Anwendung sie gebrauchen kann (siehe Kapitel 6.2.12 "Benannte Pipe für zwei
Prozesse).
Benannte Pipes kann man darüber hinaus in verschiedene Arten unterteilen. Ein
wesentliches Merkmal ist dabei der Modus der angibt wie Lese- oder Schreib-Anfragen
bearbeitet werden. Beim "Non-Blocking" Modus wird eine Lese- oder Schreib-Anfragen
solange fortgesetzt, bis die Pipe diese Operation beendet hat. Diese Pipes werden auch
"Polling"-Pipes genannt. Meistens werden Pipes im "Blocking"-Modus mit implementierten
asynchronen Lese- und Schreib- Operationen geöffnet.
Benannte Pipes können als Byte-Pipe oder Nachrichten-Pipe geöffnet werden. Bei einer
Byte-Pipe werden die Daten einfach als Byte Strom in die Pipe geschrieben. Bei einer
Nachrichten-Pipe werden die Daten beim Schreiben in Pakete unterteilt. Diese Pakete
können dann als Datenstücke gelesen werden.
Wird eine Pipe als Byte-Pipe erzeugt, so kann aus ihr nur als Byte-Pipe gelesen werden.
Wird eine Nachrichten-Pipe erzeugt, so kann aus ihr als Byte-Pipe oder als NachrichtenPipe gelesen werden.
Bei der Verwendung der "write-through" Option wartet eine Pipe auf das Ergebnis einer
Schreib-Operation bis die Daten übermittelt wurden. Eine Nachrichten-Pipe hat diese
Option immer, während sie bei einer Byte-Pipe eingestellt werden muß.
56
3 Betriebssystem Windows NT 4.0
Beispiel CreateNamedPipe():
HANDLE hPipe = CreateNamedPipe ( TEXT("\\\\.\\pipe\\pipe_name"),
PIPE_ACCESS_DUBLEX,
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE,
PIPE_UNLIMITED_INSTANCES,
4096,
4096,
INFINITE,
NULL
);
Parameter:
• Name der Pipe. Der Name einer benannten Pipe hat folgendes Format:
\\machine_name\pipe\pipe_name. Ein Punkt anstelle des machine_name
bedeutet, daß die Pipe auf den eigenen Rechner verweist.
Bei Pipenamen unterscheidet Windows NT nicht zwischen Groß- und Kleinschreibung.
• Lese-, Schreib-, Sicherheitsattribute für die Pipe.
• ACCESS_SYSTEM_SECURITY
Legt fest, daß die Client-Seite der Pipe
Schreibzugriff auf das ACL-System der
benannten Pipe haben darf.
• FILE_FLAG_OVERLAPPED
Legt fest, ob Lese-, Schreibe- und
Verbindungsoperationen asynchron mit einer
OVERLAPPED-Struktur ausgeführt werden soll.
• FILE_FLAG_WRITE_THROUGH
Zeigt an, daß der "Write-Through"-Modus
eingeschaltet ist.
• PIPE_ACCESS_DUBLEX
Legt eine bidirektionale Pipe an.
• PIPE_ACCESS_INBOUND
Legt fest, daß die Pipe für den eingehenden
Datenverkehr auf der Server-Seite der Pipe
verwendet werden soll.
• PIPE_ACESS_OUTBOUND
Legt fest, daß die Pipe für den ausgehenden
Datenverkehr auf der Server-Seite der Pipe
verwendet werden soll.
• WRITE_DAC
Zeigt an, daß die Client-Seite der Pipe
Schreibzugriff auf die "Discretionary Access
Control List" der benannten Pipe hat.
• WRITE_OWNER
Zeigt an, daß die Client-Seite der Pipe
Schreibzugriff auf den Eigentümer der
benannten Pipe hat.
57
3 Betriebssystem Windows NT 4.0
• Der Pipe-Modus und die "Blocking" Art der Pipe
• PIPE_WAIT
Zeigt, daß der "Blocking" Modus eingeschaltet
ist.
• PIPE_NOWAIT
Zeigt, daß der
eingeschaltet ist.
• PIPE_READMODE_BYTE
Daten werden als Bytefluß aus der Pipe
gelesen.
• PIPE_READMODE_MESSAGE
Daten werden als Nachrichtenfluß aus der
Pipe gelesen.
• PIPE_TYPE_BYTE
Daten werden als Bytefluß in die Pipe
geschrieben.
•
Daten werden als Nachrichtenfluß in die Pipe
geschrieben.
PIPE_TYPE_MESSAGE
"Non-Blocking"
Modus
• Die Anzahl der möglichen Instanzen der Pipe. UNLIMITED_PIPE_INSTANCES für eine
uneingeschränkte Anzahl.
• Anzahl der Byte die für den Ausgabepuffer reserviert werden soll.
• Anzahl der Byte die für den Eingabepuffer reserviert werden soll.
• Time-Out-Zeit der Pipe in Millisekunden. INFINITE bedeutet kein Zeitlimit.
• Pointer auf eine SECURITY_ATTRIBUTES-Struktur. Bei NULL benutzt der Pipe-Handle
den Sicherheitsdeskriptor des aktuellen Zugriffs-Tokens.
Als Rückgabewert dieser Funktion erhält man einen Handle auf die erzeugte Pipe oder
INVALIDE_HANDLE_VALUE, wenn die Pipe nicht erstellt werden konnte.
58
3 Betriebssystem Windows NT 4.0
Beispiel ConnectNamedPipe():
BOOL fConnected = ConnectNamedPipe ( hPipe,
NULL
);
if ( fConnected || GetLastError() == ERROR_PIPE_CONNECTED )
{
// Mit einer Anwendung verbunden
}
else
{
// Fehlermeldung
}
Parameter:
•
Handle der Pipe.
•
Die Adresse einer OVERLAPPED-Struktur oder NULL.
Der Rückgabewert dieser Funktion ist TRUE, wenn ein Client erfolgreich mit der Pipe
verbunden wurde. Tritt ein Fehler auf wird FALSE zurückgegeben.
Versucht ein Client eine Pipe zu kontaktieren die zwar erzeugt wurde aber bei der der
Server-Prozess der Pipe noch nicht die ConnectNamedPipe() Funktion aufgerufen hat
so ist der Rückgabewert FALSE und GetLastError() gibt ERROR_PIPE_CONNECTED
zurück.
59
3 Betriebssystem Windows NT 4.0
Beispiel CreateFile():
HANDLE hPipe = CreateFile ( TEXT("\\\\.\\pipe\\pipe_name"),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL
);
if ( hPipe == INVALID_HANDLE_VALUE )
{
// Fehlermeldung
}
Parameter:
• Vollständiger Pipe Name.
• Lese- und Schreibzugriff für den Pipe-Handle.
• Der "share"-Modus. Bei benannten Pipes immer 0.
• Pointer auf eine SECURITY_ATTRIBUTES-Struktur. Bei NULL benutzt der Pipe-Handle
den Sicherheitsdeskriptor des aktuellen Zugriffs-Tokens.
• Die "Handle-Creation"-Information- Bei benannten Pipes immer OPEN_EXISTING.
• Flag für Dateiattribute. Bei benannten Pipes immer 0.
• Die Adresse einer OVERLAPPED-Struktur, wenn die Pipe im OVERLAPPED-Modus
geöffnet wurde ansonsten NULL.
Als Rückgabewert diese Funktion erhält man einen Handle auf die geöffnete Pipe oder
INVALIDE_HANDLE_VALUE, wenn die Pipe nicht erstellt werden konnte.
60
3 Betriebssystem Windows NT 4.0
Öffnet man einen Pipe-Handle mit der Funktion CreateFile() so wird dieser PipeHandle als Byte-Pipe geöffnet. Möchte man jedoch eine Pipe als Nachrichten-Pipe
verwenden so muß die Funktion SetNamedPipeHandleState() angewendet werden.
Beispiel SetNamedPipeHandleState():
DWORD dwPipeState = PIPE_READMOD_MESSAGE;
BOOL fChangedState = SetNamedPipeHandleState ( hPipe,
&dwPipeState,
NULL,
NULL,
);
Parameter:
• Handle der benannten Pipe
• Ein Pointer auf die Variable in der der neue Pipe Mode steht.
• Ein Pointer auf eine Variable in der die maximale Abzahl der Bytes steht die vor dem
Übermitteln der Daten vom Client Rechner zum Server angesammelt werden darf. Hier
kann auch NULL angegeben werden, wenn dieser Parameter nicht berücksichtigt
werden soll.
• Ein Pointer auf eine Variable wo eine maximale Zeit in Millisekunden angegeben wird.
Innerhalb dieser Zeit müssen Daten über das Netzwerk zum Server übermittelt werden.
NULL muß hier angegeben werden, wenn der Pipe Handle das "Server-Ende" der Pipe
ist oder der Server- und Client-Prozess auf dem selben Rechner sind. NULL kann
verwendet werden, wenn der vorherige Parameter für die maximale Anzahl der Bytes
(s.o.) auch NULL ist.
Der Rückgabewert dieser Funktion ist TRUE wenn sie erfolgreich ausgeführt wurde. Bei
einem Fehler wird FALSE zurückgegeben.
61
4 Betriebssystem Unix / Linux
4 Betriebssystem Unix / Linux
4.1 Grundlegende Eigenschaften
Unix und Linux sind Betriebssysteme, die auf der gesamten Bandbreite der heutigen
Computer eingesetzt werden können. Im PC Bereich und auf Kleincomputer (PocketPC‘s) wird häufig Linux verwendet. Beim gewerblich genutzten Computer (Großrechner)
wird Unix verwendet.
Unix und Linux sind Allzweck-Systeme. Sie sind geeignet für die Entwicklung von Systemund Anwendersoftware. Durch eine verständliche und auch oft einfache
Programmierschnittstelle ist das Schreiben von Programmen, insbesondere von
Anfängern und Privatpersonen, einfacher, als z.B. unter Windows NT.
Beide Betriebssysteme, Unix und Linux, unterstützen
Mehrbenutzer- und
Mehrprogrammbetrieb. Sie besitzen beide ein geräteunabhäniges hierarchisches
Dateisystem. Dienstprogramme, für beide Systeme, sowie Treiber für fast jede Hardware,
sind heutzutage in großer Anzahl erhältlich. Linux ist frei erhältlich. Für umfangreiche
Komplettpakete (z.B. von SuSE) muß nur ein Bruchteil bezahlt werden, von dem was
andere Betriebssysteme kosten. Linux erfreut sich im Privatbereich immer gößerer
Verbreitung, da es eine günstige und vollständige Alternative ist.
4.2 Prozesse / Threads in Unix / Linux
Die beiden Betriebssyteme Unix und Linux sind Multitasking-Systeme. Dies bedeutet, daß
mehrere Anwendungen und Hintergrundprozesse parallel aktiv sein können. Es ist aber
nicht immer möglich, daß eine Anwendung (Prozess) selbst aus mehreren "parallel"
ablaufenden Teilen besteht. Auf klassischen Unix-Systemen werden derartige Probleme
so gelöst, daß ein Programm mehrere Prozesse erzeugt, die dann in einer eigenen
Umgebung, mit einem eigenen Speicherbereich etc. ablaufen und über die üblichen
Inteprozesskommunikationsprimitiven miteinander kommunizieren. Diese Prozesse
werden Kind-Prozesse genannt. Die Verwaltung solcher Kind-Prozesse kostet jedoch Zeit
und Systemressourcen.
In Linux kann man, da die Kind-Prozesse einer Anwendung inhaltlich eng aneinander
gebunden sind und auch auf gemeinsame Variablen zugreifen sollten, die sogenannten
Threads (Light-Weight-Processes) verwenden.
Threads unter Linux kann man gemeinsame Speicherbereiche zuteilen. Bestimmte
Speicherbereiche können als sog. "Shared Memory" vereinbart werden.
System intern wird in Linux allerdings kein Unterschied zwischen einem Thread und
einem Prozess gemacht. Ein Thread ist für Linux ein Prozess, der seinen Kontext ganz
oder teilweise mit einem anderen Prozess teilt.
62
4 Betriebssystem Unix / Linux
In dieser Diplomarbeit wird zum "parallelen" Abarbeiten von Programmteilen die
klassische Methode, die Erzeugung von mehreren Kind-Prozessen, besprochen. Diese
Art kann sowohl bei allen gängigen Unix-Systemen als auch bei einem Linux-System
angewendet werden.
4.3 Zustände von Prozessen
Abb. 4.1: Zustandsdiagramm von Prozessen in Unix / Linux [Vogt 2001]
Während seiner Existenz kann ein Prozess in Unix / Linux mehrere Zustände annehmen.
Ein Zustandswechsel wird dabei durch ein Ereignis ausgelöst.
Der Zustand rechnend besagt, daß der Prozess gerade von der CPU bearbeitet wird.
Der Zustand bereit besagt, daß der Prozess zwar bereit ist, aber auf die Benutzung der
CPU wartet.
Der Zustand blockiert besagt, daß der Prozess auf das Eintreten eines bestimmten
Ereignisses wartet (z.B. Benutzereingabe) bevor er weiter ausgeführt werden kann.
Bei Unix / Linux gibt es neben den oben genannten Zuständen eine etwas abgewandelte
Version. Der Zustand rechnend wird aufgeteilt in einen User Mode und einen
Kernel Mode. Der Übergang zwischen diesen beiden Zuständen erfolgt durch den Aufruf
einer Systemfunkion bzw. die Rückkehr von derselben. Man beachte, daß der Übergang
in einen anderen Zustand nur aus dem Kernel Mode möglich ist und nicht aus dem
User Mode.
Ein weiterer Zustand ist Zombie. In diesen Zustand gelangen Prozesse, wenn z.B. ein
Sohnprozess terminiert und der Vater noch keinen wait() Aufruf durchgeführt hat, da
erst dann ein Prozess vollständig gelöscht wird. Terminiert ein Vaterprozess ohne
wait() Aufruf, so erbt der Init-Prozess diese Prozesse und sorgt durch wait() Aufrufe
für die vollständige Löschung dieser Zombies nachdem sie terminiert sind.
63
4 Betriebssystem Unix / Linux
4.4 Priorität und Scheduling
Es werden zwei Arten von Scheduling unterschieden, unterbrechendes und nicht
unterbrechendes Scheduling. Unix und Linux benutzen ein unterbrechendes Scheduling,
so das ein Prozess vom Betriebssystem unterbrochen werden kann, damit ein anderer
Prozess von der CPU ausgeführt wird. Beide Betriebsysteme arbeiten allerdings mit
unterschiedlichen Systemen. Unix benutzt ein prioritätgesteuertes Scheduling bei dem die
Priorität der einzelnen Prozesse vom System anhand mehrerer Faktoren in bestimmten
Zeitabständen berechnet wird. Diese Faktoren sind z.B. wie rechenintensiv der Prozess
bis zu diesem Zeitpunkt war oder wie lange der Prozess schon warten mußte. Der nice
Wert, dem man beim Starten eines Prozesses vorgeben kann wird ebenfalls in diese
Berechnung einbezogen. In Unix bedeutet i.a. ein höherer Prioritätswert eine niedrige
“Dringlichkeit“.
Linux verwendet eine Mischung aus verschiedenen Schedulingklassen.
Diese Klassen sind:
1. SCHED_FIFO:
Bei Prozessen der selben Priorität wird der älteste Prozess als
erstes bearbeitet.
2. SCHED_RR:
Bei Prozessen der selben Priorität wird durch eine Zeitscheibe
bestimmt wie lange jeder Prozess die CPU erhält.
3. SCHED_OTHER:
In dieser Klasse wird durch eine Rechenformel die Reihenfolge
bestimmt. In dieser Formel wird z.B. die CPU-Zeit, die der Prozess
bereits erhalten hat berücksichtigt.
Linux unterscheidet hierbei zwei Klassen von Prozessen dringende “Realzeitprozesse“
und weniger dringende “Prozesse“. Realzeitprozesse besitzen eine Realzeitpriorität, die
entscheidet in welcher Reihenfolge die Prozesse abgearbeitet werden. Je größer dieser
Realzeitprioritäswert, desto “dringender“ ist dieser Prozess. Diese Prozesse werden in der
SCHED_FIFO und SCHED_OTHER Klasse verwaltet. Weniger “dringende“ Prozesse
erhalten die Realzeitpriorität 0 und werden in der Schedulingklasse SCHED_OTHER
verwaltet. Diese Prozesse erhalten erst dann Rechenzeit, wenn keine Realzeitprozesse
bearbeitet werden müssen [Vogt2001].
Obwohl beide Betriebssysteme unterschiedliche Methoden anwenden haben sie das
gleiche Ziel. Sie versuchen alle Prozesse so zu koordinieren, das auch, wenn das System
ausgelastet ist, möglichst jeder Prozess, auch wenn es ein weniger wichtiger ist, die CPU
zugeteilt bekommt wenn dadurch “dringende“ Prozesse nicht behindert werden.
64
4 Betriebssystem Unix / Linux
4.5 Erzeugung und Steuerung von Prozessen
Prozesse werden in Unix erzeugt, indem ein bestehender Prozess die Funktion fork()
aufruft. Das gilt nicht für Prozesse, die vom Systemkern automatisch beim Hochlaufen
des Systems erstellt werden, wie z.B. den Swapper.
Start
fork()-Return
im
Vaterprozess
wait()
fork()-Return
im
Sohnprozess
exit()
fork()
hier laufen die
Prozesse asynchron
Abb. 4.2: Erzeugen eines Sohnprozesses und Warten auf dessen Beendigung
[GuOb1995]
Der neue, von fork() kreierte Prozess, wird Kindprozess genannt. Er ist eine Kopie des
aufrufenden Prozesses und erhält z.B. eine Kopie des Datenbereiches des
Elternprozesses. Die beiden Prozesse teilen sich aber diese Speicherbereiche nicht. Die
Funktion wird nur einmal aufgerufen, liefert aber zwei Rückgabewerte. Dem Kindprozess
wird der Wert 0, dem Elternprozess dagegen die Prozessidentifikationsnummer (PID) des
Kindprozesses übergeben. Die PID des Kindprozesses wird dem Elternprozess
übergeben, da ein Elternprozess über mehrere Kindprozesse verfügen kann. Es gibt
keine zusätzliche Funktion, mit der ein Elternprozess die PID der Kindprozesse ermitteln
kann. Ein Kindprozess kann aber immer nur einen Elternprozess haben, daher erhält der
Kindprozess immer den Rückgabewert 0.
Da fork() eine genaue Kopie des aufrufenden Prozesses erzeugt und startet, ist die
PID der einzige Unterschied. Anhand der unterschiedlichen PID's, die Vater- und
Sohnprozess besitzen, können sie in verschiedene Programmstücke verzweigen.
Ein Prozess beendet sich selber, wenn er das Ende des Programmcodes erreicht oder die
Schnittstelle exit() aufruft. Bei dem Aufruf exit() wird ein ganzzahliger Parameter
(z.B. als Fehlercode) an den Vaterprozess übergeben.
Prozesse können aber auch durch andere Prozesse mit Hilfe von Signalen beeinflußt
werden, falls sie die Berechtigung dazu haben.
Die Schnittstellenfunktion hierfür lautet kill().
Signale können auch mit der Funktion pause() zur Synchronisation eingesetzt werden
(siehe Kapitel 4.7.2 Synchronisation durch Signale).
65
4 Betriebssystem Unix / Linux
Einige Schnittstellenfunktionen im Zusammenhang mit Prozessen sind:
execv
Aufruf eines Programmes aus einem Prozess
Prototyp:
int execv ( char *pfad, char *argv[ ] );
Parameter:
char *pfad
Name bzw. Pfad des Programms
char *argv[ ]
Zeiger auf das Feld, in dem die Argumente für das
aufgerufene Programm stehen.
Das letzte Element muss ein NULL-Pointer sein.
exit
Beendet den Prozess.
Prototyp:
void exit ( int status );
Parameter:
int status
Beispiel:
exit(0);
fork
Starten einen neuen Prozess.
Prototyp:
int fork ( );
Rückgabe:
0 an Kindprozess, Prozessidentifikationsnummer (PID) des Kindes an
Elternprozess, -1 bei Fehler.
Beispiel:
int kind_PID;
if ((kind_PID= fork()) == 0)
{
** Programmcode wird vom Sohnprozess ausgeführt **
}
getpid
Liefert die Prozessidentifikationsnummer (PID) des aufrufenden Prozesses
bzw. -1 bei einem Fehler.
Prototyp:
int getpid ( );
Beispiel:
printf("Meine PID ist: %i\n",getpid());
/* Gibt die eigene PID aus */
Status der zurückgegeben wird (0 = OK)
/* Fehlerfreie Terminierung des Prozesses */
66
4 Betriebssystem Unix / Linux
getppid
liefert die Prozessidentifikationsnummer des Vaterprozesses (PPID)
bzw. -1 bei einem Fehler.
Prototyp:
int getppid ( ) ;
Beispiel:
printf("Die PID meines Vaters ist: %i\n",getppid());
/* Gibt PID des Vaters aus */
kill
Das Signal sig wird durch diese Funktion an den Prozess mit der
Prozessidentifikationsnummer pid geschickt.
Includes :
#include <signal.h>
Prototyp:
int kill ( int pid, int sig ) ;
Parameter:
int pid PID des Empfänger-Prozesses
int sig
Beispiel:
Signalnummer
SIGKILL bzw. 9 terminiert den Prozess, ohne daß er die
Möglichkeit hat, das Signal abzufangen. Andere Signale wie
z.B. SIGUSR1 können vom Empfängerprozess abgefangen
werden und bewirken eine Ausführung des "Signal-Handlers"
eine vom Benutzer definierte Funktion.
int kind_PID;
/* terminiert den Prozess,*/
kill (kind_PID,SIGKILL); /* dessen PID in der */
/* Variable kind_PID gespeichert ist. */
Beispiel zur Synchronisation: siehe Kapitel 4.7.2 Synchronisation durch Signale.
pause
Der Prozess wird angehalten und wartet auf ein Signal.
Prototyp:
int pause ( );
Beispiel:
siehe Synchronisation durch Signale.
signal
Diese Funktion bindet das Signal sig an einen Signal Handler.
Includes :
#include <signal.h>
Prototyp:
void signal ( int sig, int *sighand );
Parameter:
int sig
Signal, welches an den Signal Handler gebunden werden
soll.
int *sighand Signal Handler der ausgeführt werden soll.
Beispiel:
siehe Synchronisation durch Signale.
67
4 Betriebssystem Unix / Linux
sleep
Der aufrufende Prozess blockiert für eine bestimmte Zeit.
Prototyp:
unsigned sleep ( int sec );
Parameter:
int sec Dauer in Sekunden.
Beispiel:
sleep(10);
wait
Es wird auf die Beendigung eines Sohnprozesses gewartet.
Hat ein oder mehrere Sohnprozesse bereits terminiert, so kehrt der Aufruf
sogleich zurück. Ist dies nicht der Fall wird auf die Beendigung des
nächsten Sohnprozesses gewartet.
Prototyp:
int wait ( int *statusp );
Parameter:
int *statusp Zeiger auf Variable, in der der Terminierungsstatus
des Sohnes zurückgegeben wird.
Benötigt man den Rückgabestatus nicht, kann 0 als
Parameter benutzt werden.
Rückgabe:
PID des terminierten Sohnes bzw. -1 wenn kein Sohnprozess existiert oder
bereits
terminierte
Söhne
durch
frühere
wait()
Aufrufe
entgegengenommen wurden.
waitpid
Es wird auf die Beendigung eines bestimmten Sohnprozesses gewartet.
Prototyp:
int waitpid ( int pid, int * statusp, int optionen );
Parameter:
int *statusp
siehe wait() Aufruf
int pid
pid > 0, PID des Prozesses auf den gewartet werden
soll. Bei -1 wird auf die Beendigung eines beliebigen
Sohnprozesses gewartet.
int optionen
Der Parameter bestimmt wie und worauf gewartet
werden soll. Er ist aber abhänig davon ob das System
z.B. eine Job-Kontrolle unterstützt.
Beispiel:
/* Prozess blockiert 10 Sekunden*/
watipid(pid,0,0); /* Prozess wartet bis der */
/* Sohnprozess mit der PID = pid terminiert */
68
4 Betriebssystem Unix / Linux
4.6 Hintergrundprozesse "Dämons"
Dämons werden häufig schon beim Laden des Systems gestartet und erst beim
Systemabschluß beendet. Sie laufen sozusagen im Hintergrund, da sie über kein
steuerndes Terminal verfügen. Sie werden häufig für Systemaktivitäten benötigt, die
nebenläufig zu Benutzerprogrammen ablaufen z.B. warten auf Mails.
Ein Dämon entsteht auch, wenn durch fork() ein Sohnprozess gestartet wurde und der
Eltenprozess beendet wird bevor der Sohnprozess terminiert. Dies kann mit dem Aufruf
von wait() vor dem Beenden des Vaterprozesses verhindert werden.
4.7 Synchronisation
In Unix / Linux gibt es verschiedene Arten und Methoden, um Prozesse zu
synchronisieren.
Die wichtigsten Methoden werden hier genauer besprochen.
4.7.1 Synchronisation durch Semaphore
Ein Semaphor ist ein Zähler der mehreren Prozessen gleichzeitig den Zugriff auf ein
gemeinsames Datenobjekt ermöglicht. Es wird geregelt, wieviele Prozesse gleichzeitig
eine gemeinsame Ressource benutzen dürfen.
Ein Beispiel aus dem Leben ist z.B. die Benutzung eines Aufzuges, in dem maximal 5
Personen Platz haben. Ein Semaphor wäre hier ein Zähler, der auf 5 initialisiert wird und
jedesmal, wenn eine Person den Aufzug betritt, wird er um 1 herabgezählt
(dekrementiert). Verlässt eine Person den Aufzug, dann wird der Zähler um 1 erhöht
(inkrementiert). Ist der Zähler auf 0, so darf keine weitere Person den Aufzug betreten.
Um eine gemeinsam genutzte Ressource zu verwenden, muß ein Prozess
Schritte ausführen:
folgende
1. Der Semaphor, der die Ressource kontrolliert muß abfragt werden.
2. Wenn der Semaphor einen Wert größer als 0 hat dekrementiert der Prozess den
Semaphor um 1 und verwendet die Ressource.
Wenn der Semaphor einen Wert von 0 hat wird der Prozess angehalten. Ein anderer
Prozess, der zur Zeit die Ressource benutzt, muß zuerst den Wert des Semaphores
beim Verlassen der Ressource um 1 inkrementieren. Jetzt kann der wartende Prozess
fortfahren mit Schritt 1.
3. Beim Verlassen der Ressource wird der Semaphor um 1 inkrementiert. Eventuell
wartende Prozesse werden jetzt fortgesetzt, die wieder mit Schritt 1 beginnen müssen.
69
4 Betriebssystem Unix / Linux
Oft werden auch binäre Semaphore eingesetzt. Sie werden mit 1 initialisiert und bewirken
so den wechselseitigen Ausschluß von einer Ressource.
Diesen wechselseitigen Ausschluß kann man auch mit Spinlocks lösen (siehe
wechselseitiger Ausschluß mit Spinlocks). Bei der Verwendung von Semaphoren werden
die Prozesse, falls ein kritischer Bereich belegt ist, angehalten. Sie warten im Zustand
blockiert, bis der kritische Bereich frei ist, ohne das System zu belasten.
Eine Semaphorgruppe wird vom System nicht automatisch gelöscht, wenn sie nicht mehr
benötigt wird. Sie muß durch den entsprechenden semctl()Aufruf im Programmcode
gelöscht werden. Von der Kommandooberfläche aus kann man sich die
Semaphorgruppen mit dem Befehl ipcs -s anzeigen lassen und eventuell nicht mehr
benötigte Semaphorgruppen mit dem Befehl ipcrm sem id (id der Semaphorgruppe)
löschen.
Schnittstellenfunktionen für die Arbeit mit Semaphoren:
semctl
Steuerfunktion für Semaphore
Includes:
#include <sys/ipc.h>
#include <sys/sem.h>
Prototyp:
int semctl ( int semid, int semnum, int befehl,
union semun arg ) ;
Parameter:
int semid
int- Wert für die Identifikation der Semaphorgruppe.
Rückgabewert von semget().
int semnum
Nummer des Semaphors.
int befehl
Gibt an, was getan wird.
SETALL Setzen aller Semaphorwerte
GETALL Auslesen aller Semaphorwerte
SETVAL Setzen eines bestimmten Semaphorwertes
GETVAL Auslesen eines bestimmten Semaphorwertes
IPC_RMID Löschen der Semaphorgruppe
union semun arg
union semun
Parameter für befehl
Wenn befehl== SETALL oder befehl== GETALL
Ist arg ein Feld vom Typ unsigned short.
In diesen Feldern stehen die Semaphorwerte, wie
sie gesetzt werden sollen bzw. wie sie ausgelesen
wurden. Bei befehl == SETVAL ist arg ein intWert für den bestimmten Semaphor.
Bei befehl == GETVAL hat arg keine Bedeutung,
da die Funktion den aktuellen Wert des einzelnen
Semaphores als Rückgabewert zurückliefert.
{ int val;
/* für SETVAL
*/
struct semid_ds buf; /* IPC_STAT /*/
/* und IPC_SET */
ushort *array } /*für GETALL u. SETALL*/
70
4 Betriebssystem Unix / Linux
Rückgabe:
Bei allen GET-Befehlen außer GETALL liefert die Funktion den
entsprechenden Wert. Bei anderen Befehlen ist der Rückgabewert gleich
NULL oder –1 bei einem Fehler.
Beispiel für die Funktion semctl():
#include <sys/ipc.h>
#include <sys/sem.h>
int semid;
unsigned short
unsigned short
initarray[0] =
initarray[1] =
initarray[2] =
initarray[3];
/* Initialisierungsfeld */
outarray[3];
/* Ausgabefeld */
3;
/* Wert des 1. Semaphores soll 3 sein */
5;
/* Wert des 2. Semaphores soll 5 sein */
1;
/* Wert des 3. Semaphores soll 1 sein */
semctl ( semid, 0, SETALL, initarray );
/* Initialisiert die Semaphore mit 3, 5 und 1 */
semctl ( semid, 0, SETVAL, 6 );
/* Initialisiert den 1. Semaphor mit 6 */
semctl ( semid, 0, GETALL, outarray );
/* Semaphorwerte werden in das Feld outarray geschrieben */
semctl ( semid, 0, IPC_RMID, 0 );
/* Löscht die Semaphorgruppe */
Achtung:
Manche Unix bzw. Linux Versionen akzeptieren bei SETALL und GETALL
keine Felder. Sie verlangen die Übergabe einer Union.
Beispiel:
union semun para ;
union semun para2 ;
unsigned short initarray[3];
unsigned short outarray[3];
initarray[0] = 3;
initarray[1] = 5;
initarray[2] = 1;
para.array = initarray;
/* Initialisierungsfeld */
/* Ausgabefeld */
/* siehe oben */
/* Zeigerzuweisung für */
/* Initialisierungsfeld */
para2.array = outarray;
/* Zeigerzuweisung für Ausgabefeld*/
semctl ( semid, 0, SETALL, para );
semctl ( semid, 0, GETALL, para2 );
71
4 Betriebssystem Unix / Linux
semget
Im System wird eine neue Semaphordatenstruktur angelegt. Bei einer
bestehenden Gruppe wird der Zugriff ermöglicht.
Includes:
#include <sys/ipc.h>
#include <sys/sem.h>
Prototyp:
int semget ( long schlüssel, int n, int modus );
Parameter:
long schlüssel
Schlüssel, der durch den Programmierer frei
definierbar ist. Wird hier IPC_PRIVATE eingegeben
und im Parameter modus IPC_CREAT so wird auf
jeden Fall eine neue Semaphorengruppe angelegt.
int n
Anzahl der Semaphore in der Gruppe.
int modus
Bitmuster: Die letzten 9 Bitstellen geben die
Zugriffsrechte auf die Semaphorgruppe an.
Rückgabe:
-1 bei einem Fehler.
int-Wert für die Identifikation der Semaphorgruppe.
Beispiele:
#include <sys/ipc.h>
#include <sys/sem.h>
int semid;
semid = semget ( IPC_PRIVATE, 1, IPC_CREAT|0777 ) ;
/* Erzeugt Semaphorgruppe mit einem Semaphor mit
allen
Zugriffsrechten. Der interne
Identifikationswert wird an semid übergeben.*/
oder
semid = semget ( 20, 2, IPC_CREAT|0777 ) ;
/* Erzeugt unter dem externen Schlüssel “20“ eine
Semaphorgruppe mit zwei Semaphoren mit allen
Zugriffsrechten. Der interne Identivikationswert wird
an semid übergeben. */
oder
semid = semget ( 20, 2, 0777 ) ;
/*Ermittelt die unter dem externen Schlüssel “20“
erzeugte Semaphorgruppe . Der interne
Identifikationswert wird an semid übergeben. */
72
4 Betriebssystem Unix / Linux
semop
Führt die in einem Array gespeicherten Semaphorbefehle in einer atomaren
Operation aus.
Prototyp:
int semop ( int semid, struct sembuf semoparray[],
int anzahl ) ;
Parameter:
int semid
int- Wert für die Identifikation der
Semaphorgruppe.
Rückgabewert von semget().
struct sembuf semoparray[]
Zeiger auf ein Array mit
Semaphorenoperationen.
struct sembuf
{ ushort sem_num;
short sem_op;
short sem_flg;
}
/*Nummer des */
/* Elementes */
/* Operation die */
/* ausgeführt wird */
/* negativ, 0 oder */
/* positiv */
/* Legt fest was zu*/
/* tun ist falls */
/* die Operation */
/* nicht ausführbar*/
/* ist */
4.7.2 Synchronisation durch Signale
Bei der Synchronisation / Steuerung von Prozessen durch Signale kann man die
Reihenfolge festlegen, in der bestimmte Prozesse bearbeitet werden. Die Funktionen
signal(), pause() und kill() werden hierfür verwendet. Durch die Funktion
signal() wird ein Signal-Handler, der beim Eintreffen des Signals ausgeführt wird, an
das Signal gebunden. Bei größeren Programmen, die mehrere Prozesse haben, wird es
allerdings schwierig den Überblick zu behalten. Bei dieser Methode wird die Reihenfolge
festgelegt in der Prozesse bzw. Teile von Prozessen ausgeführt werden. Die “parallele“
Bearbeitung von Prozessen wird dadurch eingeschränkt. Ein weiteres Problem bei der
Arbeit mit Signalen ist, daß Signale nicht vom System gespeichert werden. Erhält ein
Prozess ein Signal bevor dieser selbst die Funktion pause() aufgerufen hat geht dieses
Signal verloren und der Prozess wartet, wenn er später die Funktion pause() aufruft,
vergeblich auf ein Signal.
Schnittstellenfunktionen für die Arbeit mit Signalen:
pause(), kill(), signal()
siehe Kapitel 4.5 Erzeugung und Steuerung von
Prozessen.
73
4 Betriebssystem Unix / Linux
Beispiel für die Synchronisation durch Signale:
#include<stdio.h>
#include<signal.h>
void sighand()
/* Signal Handler wird beim Eintreffen */
{
/* des Signales SIGUSR1 ausgefuehrt */
signal(SIGUSR1,&sighand); /* Hier wird die Bindung des Signals */
** Programmcode der beim Eintreffen des Signals ausgeführt wird**
}
/* SIGUSR1 an den Signal Handler sighand()*/
/* erneuert. Dies muß nach jedem Eingang des
/* Signals geschehen. */
main()
{
int vater_pid,prozess1_pid,prozess2_pid;
*/
/* PID‘s der Söhne */
signal (SIGUSR1,&sighand); /* Bindung des Signals SIGUSR1 */
/* an den Signal Handler sighand() */
if((prozess1_pid = fork())==0) /* Sohnprozess 1 wird erzeugt */
{
/* und gestartet */
vater_pid=getppid();
/* Sohnprozess erfragt die */
for(hilf=0;hilf<=10;hilf++)
/* PID des Vaters */
{
** Programmcode des 1. Sohnes wird durchlaufen **
}
kill(vater_pid,SIGUSR1);
/* Dem Vaterprozess wird das */
/* Signal SIGUSR1 gesendet */
exit(0);
/* 1. Sohnprozess terminiert */
}
if((prozess2_pid = fork())==0) /* Sohnprozess 2 wird */
{
/* erzeugt und gestartet */
pause();
/* Sohnprozess 2 wartet auf ein Signal */
for(hilf=0;hilf<=10;hilf++)
{
** Programmcode des 2. Sohnes wird durchlaufen **
}
exit(0);
/* 2. Sohnprozess terminiert */
}
pause();
/* Vaterprozess wartet auf ein Signal */
kill(prozess2_pid,SIGUSR1); /* Vaterprozess sendet Signal */
return 0;
/* an 2. Sohnprozess */
}
74
4 Betriebssystem Unix / Linux
4.7.3 Wechselseitiger Ausschluß mit Spinlocks
Der Begriff „Spinlock“ ist sehr treffend (engl. Spin = drehen, kreisen). Da bei dieser Art
des wechselseitigen Ausschlußes eine while-Schleife der Hauptbestandteil ist. Diese
wird sooft durchlaufen, bis der Prozess Zugriff auf die benötigte Ressource erhält.
Man unterscheidet zwei Arten.
1. Spinlocks durch Maschinenbefehle
Hier wird durch Aufruf einer Funktion innerhalb einer while-Schleife
( while(TEST_AND_SET(&lock)); ) getestet, ob ein kritischer Bereich frei ist oder
nicht. Dies wird anhand einer Sperrvariablen festgestellt. Wenn der kritische Bereich
besetzt ist, liefert die Funktion z.B. den Wert TRUE zurück, sodaß die while-Schleife
erneut durchlaufen wird.
Ist der kritische Bereich frei, so erhält man den Wert FALSE und die Sperrvariable wird
auf TRUE gesetzt. Hierbei ist wichtig, daß die Überprüfung und das Setzen der Variable
ohne Unterbrechung also "atomar" erfolgt.
Beim Verlassen des kritischen Bereiches setzt der Prozess die Sperrvariable einfach
auf FALSE ( lock = FALSE ).
2. Spinlocks durch Lock Files
Bei dieser Art von Spinlock wird anstelle einer Sperrvariable ein sogenannter
Lock File verwendet. Dieser Lock File ist eine Datei mit einem bestimmten Namen.
Dieser Name muß allen Prozessen bekannt sein die sich einen kritischen Bereich
teilen. Ein Prozess versucht vor dem Eintritt in einen kritischen Bereich diese Datei
anzulegen. Existiert diese Datei, ist der kritische Bereich bereits von einem anderen
Prozess belegt. Verläßt dieser den kritischen Bereich, löscht er die Datei, die er beim
Eintritt in diesen Bereich angelegt hat. Durch die while-Schleife versuchen die
Prozesse solange diese Datei anzulegen, bis es ihnen gelingt. Das Erzeugen diese
Lock Files muß “atomar“ erfolgen.
Die Verwendung von Spinlocks zum gegenseitigem Ausschluß hat aber einige Nachteile.
Es wird keine Reihenfolge festgelegt, so das es passieren kann, das einige Prozesse sehr
lange warten müssen. Nach Freigabe des kritischen Bereiches besitzt jeder Prozess,
auch der den kritischen Bereich gerade verlassen hat, die selbe Wahrscheinlichkeit den
kritischen Bereich als nächstes zu erhalten.
Ein weiterer und auch größerer Nachteil ist, daß die Prozesse nicht in den Zustand
blockiert übergehen sondern immer wieder die while-Schleife durchlaufen, was zu einer
Belastung des Systems beiträgt, da sie die CPU belasten.
75
4 Betriebssystem Unix / Linux
4.8 Prozesskommunikation
Prozesse müssen die Möglichkeit haben Informationen untereinander auszutauschen
obwohl sie unabhängig voneinander sind. Diese Kommunikation kann in Unix / Linux mit
verschiedenen Methoden erreicht werden.
Da es Pipes nicht nur in Unix / Linux gibt sondern auch in Windows NT und Java wird in
dieser Diplomarbeit der Schwerpunkt auf diese Art der Kommunikation gelegt.
4.8.1 Pipes
Pipe
Schreibprozess
Bytestrom
000101100110110
Schreibende
Leseprozess
Leseende
Abb. 4.3: Pipe in Unix / Linux [Vogt 2001]
Eine Pipe kann man sich als einen röhrenartigen Datenkanal vorstellen. Ein Prozess
schreibt die Daten in diese Pipe und ein anderer Prozess kann diese Daten in der
Reihenfolge auslesen, in der sie vom anderen Prozess geschrieben wurden. Eine Pipe in
Unix / Linux ist unidirektional, so daß die Daten nur in eine Richtung übermittelt werden.
Eine Pipe hat für einen Prozess das Aussehen einer Datei, auf die er schreibt oder liest.
Außer dem Positionieren kann darauf jede Dateioperation erfolgen. Beim Lesen und
Schreiben muß allerdings auf die Größe des Pipe-Buffers geachtet werden. Dieser PipeBuffer ist abbhänig vom System ( meistens 4kB oder 8kB groß ).
Prozesse, die mit einer Pipe arbeiten werden in bestimmten Situationen vom System
gesteuert. Ein Prozess, der aus einer leeren Pipe lesen will muß warten, bis von einem
anderen Prozess in die Pipe geschrieben wurde. Ein Prozess, der in eine Pipe schreiben
will muß warten, wenn der Pipe-Buffer voll ist.
In Unix / Linux gibt es zwei Arten von Pipes, unbenannte und benannte.
Die unbenannte Pipe (manchmal auch einfache Pipe genannt) hat einige
Einschränkungen.
Die Lebensdauer einer unbenannten Pipe ist abhängig von der Lebensdauer der
Prozesse die mit ihr arbeiten. Sind alle Prozesse beendet, die mit der Pipe arbeiten, so
wird die Pipe gelöscht.
Es gibt meistens einen schreibenden und einen lesenden Prozess.
76
4 Betriebssystem Unix / Linux
Die Kommunikation über eine unbenannte Pipe ist nur für Prozesse möglich, die mit
einander “verwandt“ sind. Dies gilt für Prozesse, die eine Vater Sohn Beziehung
zueinander besitzen, für Sohnprozesse die den selben Vater haben und für
Enkelprozesse. In allen Fällen richtet der Vaterprozess die Pipe ein.
Mit einem pipe() Aufruf besitzt ein Prozess eine Pipe zu sich selbst, aus der er mit
fd[0] Daten lesen kann. Mit fd[1] kann er Daten in diese Pipe schreiben.
Prozess A
fd[1]
fd[0]
Abb. 4.4: Vaterprozess (Prozess A) richte Pipe ein [Herold1994]
Diese Pipe erhält dann einen Sinn, wenn der Vaterprozess durch einen fork() Aufruf
einen Sohnprozess kreiert, der mit dem Vaterprozess Daten austauscht. Dieser
Sohnprozess erbt die Pipe seines Vaters. Die Abbildung 4.5 zeigt die Verwendung einer
Pipe zwischen Vater und Sohn. Der Sohnprozess (Prozess B1) sendet Daten an den
Vaterprozess.
Die Richtung des Datenstromes wird dadurch beeinflußt welcher Prozess die Lese-bzw.
Schreibseite der Pipe schließt.
Mit dem Aufruf close(fd[0]) wird vom Sohnprozess die Leseseite der Pipe
geschlossen. Der Vaterprozess schließt die Schreibseite der Pipe mit dem Aufruf
close(fd[1]).
(Vater)
Prozess A
fork()
fd[0]
Schreiber
close (fd[1])
Prozess B1
fd[1]
close (fd[0])
Abb. 4.5: Herstellen einer Pipe zwischen Vater und Sohn [Herold1994]
77
4 Betriebssystem Unix / Linux
Sollen zwei Söhne durch eine unbenannte Pipe miteinander kommunizieren wie es die
Abbildung 4.6 zeigt, so müssen folgende Schritte ausgeführt werden.
1.
2.
3.
4.
5.
6.
7.
Vaterprozess richtet durch den Aufruf pipe() eine Pipe ein.
Der Vaterprozess kreiert durch fork() einen “Schreib-Sohn“.
Der Vaterprozess schließt durch close(fd[1]) die Schreibseite der Pipe.
Der “Schreib-Sohn“ schließt die Leseseite durch close(fd[0]).
Der Vaterprozess kreiert nun durch fork() einen “Lese-Sohn“.
Der Vaterprozess schließt durch close(fd[0]) nun auch die Leseseite der Pipe.
Dieser “Lese-Sohn“ schließt durch close(fd[1]) die Schreibseite der Pipe.
(Vater)
Prozess A
fork()
close (fd[0])
fork()
Leser
Schreiber
Prozess B1
fd[1]
Prozess B2
fd[0]
close (fd[1])
Abb. 4.6: Herstellen einer Pipe-Verbindung zwischen “Schreib-Sohn” und “Lese-Sohn”
[Herold1994]
Die so erstellte Pipe bildet nun eine Kommunikationsverbindung zwischen dem 1. Sohn
(Schreibprozess) und dem 2. Sohn (Leseprozess). Der Vaterprozess hat nach dem
Erstellen keinen Einfluß auf die Pipe, da er die Lese-und Schreibseite geschlossen hat.
Eine benannte Pipe ist eine Erweiterung gegenüber einer unbenannten Pipe. Sie besitzt
einen angelegten Geräteeintrag vom Typ FIFO (First In First Out) und hat einen
entsprechenden Namen, mit dem sie von jedem Prozess durch open() angesprochen
werden kann. Dieser Name wird beim Aufruf des ls –l Kommandos angezeigt und
durch ein p als Typenangabe gekennzeichnet.
Eine benannte Pipe wird vom System nicht automatisch gelöscht, wenn alle Prozesse
beendet sind. Durch den Aufruf unlink() muß der Anwender die benannte Pipe
innerhalb eines Prozesses selber löschen. Eine Löschung der benannten Pipe ist auch
von der Kommandooberfläche durch den Befehl rm möglich.
78
4 Betriebssystem Unix / Linux
Schnittstellenfunktionen für die Arbeit mit Pipes:
close
Schließt ein Schreib-oder Leseende einer Pipe
Prototyp:
int close ( int fd );
Parameter:
int fd
Beispiel:
siehe pipe() Aufruf
mkfifo
Erzeugt eine benannte Pipe.
Prototyp:
int mkfifo ( char *name, int mode );
Parameter:
char name
Name bzw. Pfad der Pipe.
int mode
Bitmuster für Zugriffsrechte auf die Pipe. Die Positon und
Bedeutung dieser Bits sind so wie bei der Ausgabe des ls –l
Kommandos.
Lese-bzw. Schreibdeskriptor einer Pipe
Rückgabe:
0 bei erfolgreicher Ausführung, ansonsten –1.
Beispiel:
mkfifo (“MY_PIPE“, 0777); /* Erzeugt eine benannte Pipe mit dem
Namen
MY_PIPE
im
selben
Verzeichnis, in dem der Prozess
gestartet wurde. 0777 ist der Oktalwert
777 das dem Bitmuster 111111111
entspricht. Damit haben allen Benutzer
sämtliche Zugriffsrecht. */
open
Öffnet eine Pipe bzw. Datei.
Prototyp:
int open ( char *name, int flag, int mode );
Parameter:
char name
Name bzw. Pfad der Pipe
int flag
Bitmuster für Zugriff auf die Pipe.
O_RDONLY Lesezugriff
O_WRONLY Schreibzugriff
O_NONBLOCK gibt an, wie sich der Prozess verhalten soll.
Wird O_NONBLOCK nicht angegeben
(Normalfall), wird ein Leseprozess
blockiert, bis ein anderer Prozess die Pipe
zum Schreiben öffnet und umgekehrt.
Rückgabe:
-1 bei Fehler oder Dateideskriptor für Pipe bzw. Dateizugriff
Beispiel:
fd = open („MY_PIPE“, O_WRONLY)
/* Öffnet die Pipe MY_PIPE zum Schreiben */
79
4 Betriebssystem Unix / Linux
pipe
Erzeugt eine unbenannte Pipe.
Prototyp:
int pipe ( int fd[2] );
Parameter:
int fd[2]
Beispiel:
main()
{
int fd[2];
char outbuf[6];
pipe(fd);
/* Pipe wird erzeugt */
** Sohnprozess erzeugen **
close (fd[0]);
/* Sohnprozess schließt */
/* Leseende der Pipe */
write (fd[1], “Hallo“, 6); /* Sohnprozess schreibt */
/* in die Pipe*/
* Sohnprozess führt weiteren Code aus und terminiert *
close(fd[1]);
/* Vaterprozess schließt */
/* Schreibende der Pipe */
read (fd[0],outbuf,6);
/* Vater liest Pipe aus */
** Vaterprozess terminiert **
}
read
Auslesen der Daten aus einer Pipe. Ist die Pipe leer, blockiert die Funktion.
Prototyp:
int read ( int fd, char *outbuf, unsigned bytes );
Parameter:
int fd
Diskriptor der Pipe.
char *outbuf
Zeiger auf den Speicherbereich, indem die Daten
gespeichert werden.
unsigned bytes
Maximale Anzahl der Bytes, die gelesen werden.
Dateideskriptoren, die zurückgegeben werden.
fd[0] Dateideskriptor für das Leseende der Pipe.
fd[1] Dateideskriptor für das Schreibende der Pipe.
Rückgabe:
Anzahl der gelesenen Bytes, -1 bei einem Fehler und 0, wenn die Pipe am
Schreibende geschlossen wurde.
Beispiel:
fd = open (“MY_PIPE“,O_RDONLY);
lese = read(fd, outb, 2); /* Liest max. 2 Bytes aus */
/* der Pipe “MY_PIPE“ */
unlink
Löscht die benannte Pipe.
Prototyp:
int unlink ( char* name );
Parameter:
char *name
Beispiel:
unlink(“MY_PIPE“); /* Die Pipe MY_PIPE wird gelöscht */
Name der Pipe.
80
4 Betriebssystem Unix / Linux
write
Schreibt Daten in eine Pipe. Ist der Pipe-Buffer voll, blockiert diese
Funktion.
Prototyp:
int write ( int fd, char *outbuf, unsigned bytes );
Parameter:
int fd
Diskriptor der Pipe.
char *outbuf
Zeiger auf den Speicherbereich, von dem die Daten
geschrieben werden.
unsigned bytes Maximale Anzahl der Bytes, die geschrieben werden.
Beispiel:
fd = open (“MY_PIPE“, O_WRONLY);/*Schreibt “HALLO“ in*/
write (fd, “HALLO“, 6);
/* die Pipe “MY_PIPE“ */
4.8.2 Message Queues
Abb. 4.7: Aufbau von Message Queues in Unix / Linux [Vogt 2001]
Bei dieser Art der Kommunikation werden die Daten an Nachrichtenspeicher, sogenannte
“Message Queues“, gesendet und können dort von anderen Prozesssen abgeholt
werden. Dies wird durch ein Array verwaltet (Message-Queue-Tabelle), indem Daten über
jede Message Queue stehen. Die Nachrichten bestehen aus einem Nachrichtenkopf
(Message Header) und einem Nachrichtentext. Im Message Header sind Informationen,
wie Typ, Größe der Nachricht und ein Zeiger auf den Speicherbereich, wo die Nachricht
steht, enthalten.
81
4 Betriebssystem Unix / Linux
4.8.3 Shared Memory
Abb 4.8: Grundprinzip von Shared Memorry [Vogt 2001]
Bei diesem Prinzip benutzen die Prozesse einen gemeinsamen Speicherbereich auf den
sie zugreifen können. Dieser Speicherbereich muß durch das Beriebssystem
gekennzeichnet bzw. registriert werden. Erfolgt der Zugriff auf diesen Speicherbereich
durch mehrere Prozesse, müssen diese synchronisiert werden.
4.8.4 Sockets
Client-Prozess
Server-Prozess
Socket-Kopf
Socket-Kopf
Protokoll-Stack
Gerätetreiber
TCP
TCP
IP
IP
z.B. Ethernettreiber
Protokoll-Stack
Gerätetreiber
z.B. Ethernettreiber
Rechnernetz
Abb. 4.9: Das Socket-Modell am Beispiel TCP/IP [GuOb1995]
82
4 Betriebssystem Unix / Linux
Ein Socket kann als Datenpunkt zur Kommunikation zwischen Prozessen betrachtet
werden. Sockets ermöglichen eine bidirektionale Kommunikation sowohl lokal als auch
innerhalb eines Netzwerkes. Der vom Benutzer aus sichtbare Teil der Kommunikation
besteht aus drei Teilen (siehe Abb. 4.9):
• dem Socket-Kopf (Socket Layer),
• dem Protokollteil (Protocol Layer),
• dem Gerätetreiber ( Device Layer).
Der Socket-Kopf bildet die Schnittstelle zwischen den Betriebssystemaufrufen und den
weiter unten liegenden Schichten. Welche Kombinationen von Sockets, Protokoll und
Treiber, möglich sind, wird bei der Systemgenerierung festgelegt.
Sockets
mit
gleicher
Charakteristika,
bezüglich
Adressierung
und
des
Protokolladreßformates, werden zu Bereichen, sogenannten Domains, zusammengefaßt.
Die Unix System Domain dient dabei zur lokalen Kommunikation zwischen Prozessen.
Die Internet Domain dient zur Kommunikation über ein Netzwerk.
Client-Prozess
Server-Prozess
listen
Socket
accept
Socket
Abb. 4.10: Client- Server -Kommunikation [GuOb1995]
Eine Kommunikation läuft in der Regel so ab, daß ein Server-Pozess einen
Kommunikationspunkt (Socket) aufbaut. Ein Client-Prozess koppelt sich ebenfalls an
einen (lokalen) Kommunikationspunkt (Socket) und beantragt einen Verbindungsaufbau
zu dem Socket des Server-Prozesses.
Der Server-Prozess macht mit dem Aufruf listen() dem System bekannt, daß er
Verbindungen akzeptieren will und gibt die Länge einer Warteschlange an. Der Aufruf
accept() erfolgt, wenn ein Client-Prozess eine Verbindung anfordert.
Der accept()-Aufruf liefert nach einem Verbindungsaufbau dem Server-Prozess einen
neuen Socket-Deskriptor (analog zu einem Dateideskriptor) für einen anderen Socket
zurück, über den nun die Kommunikation mit dem Client-Prozess erfolgen kann. Wie in
Abb. 4.10 zu sehen ist, ist der Socket, an dem der Server-Prozess auf Verbindung wartet,
und der Socket, über den nach einem Verbindungsaufbau die Kommunikation stattfindet,
auf der Serverseite nicht identisch.
83
4 Betriebssystem Unix / Linux
4.8.5 Streams
Benutzerprozess
Benutzeradreßraum
Streams-Kopf
Systemadreßraum
Modul
(optional)
Treiber
Abb. 4.11: Schemabild eines Streams [GuOb1995]
Ein Stream ist ein Pseudotreiber im Betriebssystemkern, wobei der Begriff Pseudo hierbei
verwendet wird, weil zunächst hinter dem Treiber kein physikalisches Gerät steht,
sondern nur eine Reihe von Softwarefunktionen. Der Treiber stellt dabei eine Schnittstelle
zwischen Benutzerprogramm und Beriebssystem zur Verfügung. Über diese Schnittstelle
können Daten(ströme) in beide Richtungen und volldublex ausgetauscht werden.
Ein Datenweg, der mit einem Stream-Mechanismus aufgebaut wurde besteht aus
folgenden Komponenten (siehe Abb. 4.11):
•
dem Stream-Kopf (Stream Head),
•
einem oder mehrere optionalen Verarbeitungsmodulen,
•
einem an dem Stream angekoppelten Treiber.
Der Treiber kann dabei ein Gerätetreiber für ein physikalisches Gerät oder ein
Pseudotreiber sein. Eine mögliche Funktion eines Verarbeitungsmoduls kann z.B. in
einem Netzwerk die Abarbeitung eines Netzwerkprotokolls sein.
Eine
wesentliche
Eigenschaft
des
Streams-Mechanismus
ist
der,
daß
Verarbeitungsmodule dynamisch in den Verarbeitungsstrom eingeschaltet und wieder
entfernt werden können. Wird ein neues Verarbeitungsmodul eingefügt, geschied dieses
unmittelbar hinter dem Kopfmodul. Bereits vorhandene Verarbeitungsmodule werden
dadurch nach "unten" verschoben.
84
5 Beispiel Applet
5 Beispiel Applet
5.1 Grundlegende Arbeitsweise eines Applets
Applets sind Java-Programme, die auf einem Internet / Intranet-Server gespeichert sind.
Sie werden auf verschiedene Client-Plattformen heruntergeladen und dort in einer Java
Virtual Machine (JVM) ausgeführt, die von dem auf dem Client-Rechner ausgeführten
Browser bereitgestellt wird.
Um ein Applet in einer Webseite zu integrieren benötigt man ein sogenanntes <APPLET>Tag. In diesem <APPLET>-Tag stehen Informationen, die die JVM benötigt, um das
Applet auszuführen.
Bevor das Applet gestartet wird prüft das Klassenlademodul in der JVM welche Klassen
benötigt werden. Beim Klassenladevorgang werden die Klassendateien überprüft um
sicher zu sein, daß es sich um gültige Klassendateien und nicht um bösartigen Code
handelt. Ist der Klassenladevorgang erfolgreich abgeschlossen wird das Applet
ausgeführt.
Benutzer, die ein Programm aus dem Internet herunterladen und ausführen, wollen sicher
sein, daß dieses Programm keine bösartigen Aktionen ausführt wie z. B. das Formatieren
der Festplatte oder das Öffnen von Verbindungen für "nicht vertrauenswürdige" Rechner.
Deshalb erfolgt die Verteilung und Ausführung unter Aufsicht eines Security Managers,
der Applets daran hindern kann, Aufgaben auszuführen.
Da der Security Manager viele Aktivitäten, die bei einer "normalen" Anwendung
selbstverständlich sind nicht zuläßt, muß der Entwickler eines Applets sich im Vorfeld
Gedanken darüber machen, welche Einschränkungen der Security Manager verursacht.
Standardmäßig werden alle Applets als "nicht vertrauenswürdig" betrachtet und von
bestimmten Aktivitäten ausgeschlossen. Dazu zählen:
• Lesen von und Schreiben auf die lokale Festplatte.
Es ist nicht möglich, Dateien zu lesen, ihr Vorhandensein zu überprüfen, Dateien zu
schreiben, Dateien umzubenennen usw. Dadurch wird verhindert, daß Informationen
über den Benutzer erfaßt und gespeichert werden.
• Verbinden mit einem anderen als dem Computer, von dem das Applet stammt.
Dies erschwert die Bereitstellung von Daten aus einer Datenbank, die sich auf einem
anderen Computer als der Webserver befindet.
Es gibt weitere Aktivitäten (z. B. die Ausführung von Programmen auf lokalen Systemen,
um Zeit zu sparen), die für Anwendungsentwickler selbstverständlich, in Applets jedoch
nicht erlaubt sind. In jedem brauchbaren Java-Buch werden die Einschränkungen und
eventuelle Lösungen aufgelistet, denen Applets unterliegen. [JBulider3]
85
5 Beispiel Applet
5.2 Eigenschaften des Beispiel Applets
Abb. 5.1: Screenshot von der Benutzeroberfläche des Beispiel Applets
Das Beispiel Applet in dieser Diplomarbeit soll jedem Anwender zeigen, wie sich die
Synchronisation von Threads auswirkt. Zur besseren Übersicht wurde der
Programmcode, den die einzelnen Threads ausführen einfach gehalten.
Bei diesem Applet hat man die Möglichkeit drei Zähler und eine grafische Animation zu
starten. Die drei Zähler des Applets und die grafische Animation sind unterschiedlich
realisiert worden.
Der 1. Zähler ( Counter genannt ) ist durch eine einfache Klasse mit dem Namen Counter
implementiert worden. Diese Klasse wird nicht als Thread ausgeführt sondern als
"normaler" Programmcode.
86
5 Beispiel Applet
Der 2. Zähler ( Thread 1 genannt ) ist durch die Klasse CountingThread implementiert
worden. Diese Klasse wurde jedoch von der Klasse Thread abgeleitet und die run()
Methode mit dem Programmcode des Zählers überschrieben. Dieser Zähler wird daher
als Thread ausgeführt.
Der 3. Zähler ( Thread 2 genannt ) ist durch die Klasse CountingThreadRunnable
implementiert worden. Diese Klasse besitzt das Interface Runnable und die run()
Methode wurde mit dem Programmcode des Zählers überschrieben. Dieser Zähler wird
daher als Thread ausgeführt.
Die grafische Animation ( Thread 3 genannt ) ist durch die Klasse ImageCanvas
implementiert worden. Diese Klasse wurde von der Klasse Canvas abgeleitet und besitzt
das Interface Runnable und die run() Methode wurde mit dem Programmcode für die
grafische Animation überschrieben. Die grafische Animation wird auch als Thread
ausgeführt.
Thread 3 soll eine Belastung des Systems verursachen, damit das Verhalten des Applets
auch unter verschiedenen Bedingungen beobachtet werden kann.
Die eventuelle Synchronisation findet, mit Ausnahme der "Synchronisation durch
Priorität", zwischen Thread 1 und Thread 2 statt.
Die Oberfläche des Applets kann in vier Hauptbereiche unterteilt werden :
• Eingabefelder für die drei Zähler
Über diese Felder kann eingestellt werden welchen Wertebereich die einzelnen Zähler
durchlaufen und wie lange die Pause zwischen den einzelnen Zählerschritten sein soll.
Bei den Eingabefelder für die Zähler, die als Thread ausgeführt werden kann zusätzlich
die Priorität eingestellt werden, die sie beim Starten besitzen sollen.
• Buttons zum Steuern der Threads
Die Buttons unterhalb der Eingabefelder dienen zum Starten bzw. Beenden der
Threads. Bei den Programmteilen, die als Thread ausgeführt werden kann zusätzlich
die aktuelle Priorität geändert werden.
• Ausgabefeld für die Zähler und Steuerinformationen
Über das Ausgabefeld des Applets werden die Zählerstände der Zähler und
Informationen über die Threads bzw. dem Counter ausgegeben.
Da alle Ausgaben in einem Feld erfolgen hat man später eine genauere Übersicht in
welcher Reihenfolge die Meldungen erfolgten.
87
5 Beispiel Applet
• Synchronisationsfeld in der die Synchronisationsart umgestellt werden kann
Im Synchronisationsfeld rechts neben dem Ausgabefeld kann eingestellt werden, ob
und wenn ja, auf welche Weise synchronisiert werden soll. Wird die Einstellung
geändert während die Threads laufen werden die beiden Zähler gestoppt.
Folgende Arten der Synchronisation können ausgewählt werden:
• ja, mit Ereignis
Bei dieser Art wird durch ein Ereignis synchronisiert. Als Ereignis dient die Anzahl
der Ausgaben, die ein Thread ohne Unterbrechung vornehmen kann. Diese Anzahl
kann während die Threads laufen verändert werden.
• ja, mit Priorität
Soll eine Steuerung der Threads über die Priorität erfolgen wird die "sleep-Zeit" von
Thread 1 und Thread 2 durch eine Anzahl von Schleifen ersetzt, die während der
Ausführung der Threads geändert werden kann. Dies wird gemacht, da ein Thread
während er schläft den Prozessor freigibt und so andere Threads, auch wenn sie
eine niedrigere Priorität besitzen, ausgeführt werden. Man würde ohne die
rechenintensiven Schleifen keinen Unterschied zwischen den einzelnen Threads,
die eine unterschiedliche Priorität besitzen, bemerken.
• ja, auf das Ende von Thread 2
Wie der Titel schon sagt, wird die Reihenfolge, in der die beiden Zähler Threads
ausgeführt werden, beeinflußt. Erst wenn Thread 2 beendet ist darf Thread 1 seine
Ausführung beginnen. Die Reihenfolge, in der die beiden Threads gestartet werden,
spielt hierbei eine große Rolle. Bei der Auswahl dieser Synchronisationsart können
die beiden Zähler Threads nur mit dem Button "Threads 1 u. Thread 2 starten"
gestartet werden.
• nein
Wird "nein" ausgewählt findet keine Synchronisation statt.
Zusätzlich zu diesen Bereichen findet man am unteren rechten Rand noch zwei Buttons.
Der Button "Alle Threads anhalten" bzw. "Alle Threads weiterführen" dient dazu, alle
laufenden Threads anzuhalten oder weiterzuführen, ohne das die Synchronisation
unterbrochen wird. Jeder Thread arbeitet nach der Unterbrechung so weiter wie vorher.
Der "RESET" Button versetzt die Threads und das Ausgabefeld wieder in den
Startzustand.
Führt man das Applet im Browser aus, wird die maximale Priorität der Threads vom
System so begrenzt, daß wichtige Systemprozesse weiter arbeiten können und somit das
System lauffähig bleibt (siehe Kapitel 2.6).
Wird das Applet in einem Appletviewer ausgeführt besteht diese Begrenzung nicht. In so
einem Fall kann die Priorität bis auf das Maximum (Prioritätswert 10) eingestellt werden.
Hierbei ist darauf zu achten, daß die Lauffähigkeit des Systems bei der Vergabe von zu
hohen Prioritäten eingeschränkt oder sogar gestoppt wird.
88
5 Beispiel Applet
5.3 Klassen des Applets
Die Klassen, Konstruktoren und Methoden des Beispiel Applets werden in diesem Kapitel
genauer erläutert. Im Source-Code befinden sich zusätzliche Erläuterungen, so daß nicht
alle Attribute der Klassen hier aufgelistet werden.
Auf eine zusätzliche Visualisierung durch UML (Unified Modeling Language) Diagramme,
wie z.B. einem Klassendiagramm, wurde ebenfalls verzichtet.
5.3.1 Die Klasse Applet1.java
Die Klasse Applet1 ist von der Klasse Applet abgeleitet und besitzt die Interfaces
ActionListener und FocusListener. Diese Klasse erzeugt die Eingabemaske. Die
Hauptkomponenten dieser Oberflächen sind die zahlreichen Eingabefelder
(javax.swing.JTextField), Bezeichner für die Oberfläche (javax.swing.JLabel) und die
Aktionsknöpfe (javax.swing.JButton).
Diese Komponenten werden hier nicht eingehender erläutert, da sie im Source-Code
kommentiert sind.
Attribute:
private boolean sync
Variable für Synchronisation durch Ereignisse.
private boolean syncjoin
Variable zur Synchronisation auf das Ende eines Threads.
private CountingThread thread1
Objekt für Thread 1.
private Thread thread2, thread3
Objekte für Thread 2 und Thread 3.
private Runnable rthread, bthread
Runnable Objekte für die Klassen mit dem Interface Runnable.
private int[] thindex = new int [3]
Thread Index um festzuhalten welcher Thread gestartet wurde.
89
5 Beispiel Applet
private String sleepthread1
private String sleepthread2
Variable um sleep-Zeit zu speichern, wenn durch Priorität synchronisiert wird.
private Steuerung steuer = new Steuerung(false)
Objekt um die Threads anzuhalten und wieder auszuführen.
private Beenden stop2 = new Beenden(false)
private Beenden stop3 = new Beenden(false)
stop2 ist ein Objekt der Klasse Beenden und dient dazu dem Thread 2
anzuzeigen, das er seine run() Methode beenden soll. stop3 ist für Thread 3.
private Syncprio syncprio
Diese Objekt dient zur Synchronisation durch Priorität.
private IntJTextField jTextFieldA[] = new IntJTextField[13]
Feld für 13 Textfelder (Eingabefelder).
MeineTextArea textArea1
Ausgabefeld für alle Threads und Zusatzinformationen.
Methoden:
public String getParameter(String key, String def)
Parameterwert zum Starten des Applets holen.
public Applet1()
Das Applet konstruieren.
public void init()
Das Applet initialisieren.
private void jbInit()
Initialisierung der Komponenten.
public String getAppletInfo()
Applet-Informationen werden geholt.
public String[][] getParameterInfo()
Parameter-Infos holen.
90
5 Beispiel Applet
public static void main(String[] args)
Main-Methode
public void actionPerformed(ActionEvent e)
ActionPerformed, der ausgeführt wird sobald ein Button betätigt wird.
void thread1start()
Eigene Methode um Thread 1 zu erzeugen und ggf. zu starten.
void thread2start()
Eigene Methode um Thread 2 zu erzeugen und ggf. zu starten.
void jButton1_actionPerformed(ActionEvent e)
Methode, die durch Betätigung eines Buttons ausgeführt wird, um die Priorität
von Thread 1 herabzusetzen.
void jButton2_actionPerformed(ActionEvent e)
Methode, die durch Betätigung eines Buttons ausgeführt wird, um die Priorität
von Thread 1 heraufzusetzen.
void jButton3_actionPerformed(ActionEvent e)
Methode, die durch Betätigung eines Buttons ausgeführt wird, um die Priorität
von Thread 2 herabzusetzen.
void jButton4_actionPerformed(ActionEvent e)
Methode, die durch Betätigung eines Buttons ausgeführt wird, um die Priorität
von Thread 2 heraufzusetzen.
void jButton5_actionPerformed(ActionEvent e)
Methode, die durch Betätigung eines Buttons ausgeführt wird, um die Priorität
von Thread 3 herabzusetzen.
void jButton6_actionPerformed(ActionEvent e)
Methode, die durch Betätigung eines Buttons ausgeführt wird, um die Priorität
von Thread 3 heraufzusetzen.
public void focusLost(FocusEvent e)
Methode des FocusListener wird ausgeführt sobald ein Objekt den Focus
verliert.
91
5 Beispiel Applet
public void focusGained(FocusEvent e)
Methode des FocusListener wird ausgeführt sobald ein Objekt den Focus erhält.
public boolean test()
Methode, die kontrolliert, ob alle Textfelder in ihrem Wertebereich sind.
public void setsync()
Diese Methode stellt fest ob die Ausgabe synchronisiert werden soll.
void groupBox1_itemStateChanged(ItemEvent e)
Methode wird ausgeführt, wenn durch Ereignisse synchronisiert werden soll. Sie
setzt dann die entsprechenden Parameter.
void groupBox2_itemStateChanged(ItemEvent e)
Methode wir ausgeführt, wenn durch die Priorität synchronisiert werden soll. Sie
setzt dann die entsprechenden Parameter.
void groupBox3_itemStateChanged(ItemEvent e)
Methode wird ausgeführt, wenn auf das Ende des zweiten Threads
synchronisiert wird. Sie setzt dann die entsprechenden Parameter.
void groupBox4_itemStateChanged(ItemEvent e)
Methode wird ausgeführt, wenn nicht synchronisiert werden soll.
Sie setzt dann die entsprechenden Parameter.
void jTextFieldA11_actionPerformed(ActionEvent e)
Methode wird ausgeführt, wenn eine Eingabe im Textfeld 11
erfolgte. Dieses Eingabefeld bestimmt die Anzahl der Ausgaben.
void jTextFieldA12_actionPerformed(ActionEvent e)
Methode wird ausgeführt, wenn eine Eingabe im Text Feld 12
erfolgte. Dieses Eingabefeld bestimmt die Anzahl der Schleifen.
class Applet1_jTextFieldA11_actionAdapter implements
java.awt.event.ActionListener
Klasse des action_Adapter für das Eingabefeld 11
public void actionPerformed(ActionEvent e)
Methode des action_Adapter für das Eingabefeld 11
92
5 Beispiel Applet
class Applet1_jTextFieldA12_actionAdapter implements
java.awt.event.ActionListener
Klasse des action_Adapter für das Eingabefeld 12
public void actionPerformed(ActionEvent e)
Methode des action_Adapter für das Eingabefeld 12
5.3.2 Die Klasse Beenden.java
Diese Klasse dient dazu, die Thredas 2 und 3, die mit Hilfe des Interfaces Runnable
erstellt wurden, zu beenden. Durch ein Objekt dieser Klasse können die beiden Threads
"erfragen" ob sie sich beenden sollen.
Konstruktor:
public Beenden(boolean wert)
Beim Erzeugen des Objekts muß der Anfangswert übergeben werden.
Attribut:
private boolean stop
Speichert den Wert des Objektes.
Methoden:
public void set(boolean wert)
Methode um den Wert des Objektes zu ändern.
public boolean get()
Methode um den Wert des Objektes abzufragen.
93
5 Beispiel Applet
5.3.3 Die Klasse Counter.java
Diese Klasse ist ein Zähler, der einen übergebenen Wertebereich durchläuft.
Konstruktor:
public Counter(MeineTextArea textArea1)
Beim Erzeugen wird die Text Area übergeben, auf die die Ausgabe erfolgen
soll.
Attribute:
private int start
Startwert des Zählers.
private int finish
Ende des Zählers.
private long time
Zeit, die der Zähler zwischen den Zählerschritten "schlafen" soll.
MeineTextArea textArea1
TextArea für die Ausgabe.
Methode:
public void setcounter (int from, int to, long zeit)
Daten für den Zähler werden übernommen und der Zähler wird
ausgeführt.
5.3.4 Die Klasse CountingThread.java
Diese Klasse ist von Thread abgeleitet und beinhaltet einen Zähler, der somit als Thread
ausgeführt wird.
Konstruktor:
public CountingThread(Syncprio syncprio,MeineTextArea textArea1,
Steuerung steuer,JLabel jLabel12)
Beim Erzeugen werden Objekte von anderen Klassen übergeben.
Diese Objekte werden zur Steuerung und Ausgabe des Threads benötigt.
94
5 Beispiel Applet
Attribute:
private int start
private int finish
private long time
Variablen wie bei der Klasse Counter
private boolean stop
Variable für eigene "stop"-Methode des Threads
MeineTextArea textArea1
TextArea für Ausgabe.
Steuerung steuer
Steuerobjekt zum Überprüfen, ob der Thread anhalten soll.
Syncprio syncprio
Steuerobjekt für Synchronisation durch Priorität.
JLabel jLabel12
Ausgabefeld der Priorität
Methoden:
public void meinstop()
Modifizierte "stop"-Methode.
public void setcountingthread(int from, int to,long zeit)
Methode um die Daten für den Zähler zu übernehmen.
public void joinset(Thread thread)
Modifizierte "start"-Methode. Sie dient zum Starten, wenn auf das Ende eines
andern Thread gewartet werden soll.
public void run()
run() Methode des Threads.
95
5 Beispiel Applet
5.3.5 Die Klasse CountingThreadRunnable.java
Diese Klasse beinhaltet ebenfalls ein Zähler. Sie besitzt das Interface Runnable wodurch
der Programmcode ebenfalls als Thread ausgeführt wird.
Konstruktor:
public CountingThreadRunnable(JLabel jLabel13,Syncprio
syncprio,Beenden stop2,Steuerung steuer, MeineTextArea
textArea1,int from, int to,long zeit)
Beim Erzeugen werden Objekte von anderen Klassen und die Daten für den
Zähler übernommen. Die Objekte dienen der Steuerung und der Ausgabe des
Zählers.
Attribute:
private int start
private int finish
private long time
Variablen wie bei der Klasse Counter
Beenden stop
Objekt zum Beenden des Threads
MeineTextArea textArea1
TextArea für Ausgabe.
Steuerung steuer
Steuerobjekt zum Überprüfen, ob der Thread anhalten soll.
Syncprio syncprio
Steuerobjekt für Synchronisation durch Priorität.
JLabel jLabel13
Ausgabefeld der Priorität.
Methode:
public void run()
run() Methode des Interfaces Runnable.
96
5 Beispiel Applet
5.3.6 Die Klasse ImageCanvas.java
Diese Klasse ist abgeleitet von der Klasse Canvas und besitzt das Interface Runnable.
Sie wird für die grafische Animation benötigt, die ebenfalls als Thread ausgeführt wird.
Konstruktor:
public ImageCanvas(Image images[],Steuerung steuer,Beenden stop3,
Syncprio syncprio)
Es wird ein Feld, das die Bilder enthält und Objekte anderer Klassen,
übergeben.
Attribute:
private Image images[]
Feld für die Bilder, die ausgegeben werden sollen.
private int currentimage
Variable für Bild.
Beenden stop
Objekt zum Beenden des Threads
Steuerung steuer
Steuerobjekt zum Überprüfen, ob der Thread anhalten soll.
Syncprio syncprio
Steuerobjekt für Synchronisation durch Priorität.
Methode:
public void paint(Graphics g)
Methode zum Zeichnen des Bildes.
public void update(Graphics g)
Methode zum Zeichnen des Bildes.
public void run()
run() Methode des Interfaces Runnable.
97
5 Beispiel Applet
5.3.7 Die Klasse IntJTextField.java
Diese Klasse ist abgeleitet von der Klasse JTextField.
Einige Methoden wurden ergänzt bzw. der Konstruktor so verändert, daß die Objekte
Eingabefelder mit einem festen Wertebereich sind.
Konstruktor:
public IntJTextField (int min, int max)
Konstruktor der Klasse dem beim Erzeugen des Objektes der erlaubte
Wertebereich der Eingabe übergeben wird.
Attribute:
private int low
Untere Grenze des Wertebereiches.
private int high
Obere Grenze des Wertebereiches
Methoden:
public boolean isValid()
Methode um festzustellen, ob die Eingabe des Objektes ein Int-Wert im
erlaubten Wertebereich ist.
public int getValue()
Methode zum Auslesen des Textfeldes.
98
5 Beispiel Applet
5.3.8 Die Klasse MeineTextArea.java
Die Klasse ist von der Klasse TextArea abgeleitet und dient zur synchronisierten und nicht
synchronisierten Ausgabe der Threads.
Konstruktor:
public MeineTextArea()
Parameterloser Konstruktor
Attribute:
private String buffer
Buffer für die Ausgabe.
private int soll
Anzahl der "soll" Ausgaben.
private int anzahl
Anzahl der "ist" Ausgaben
boolean th1
Variable die bei einer Synchronisation durch Ereignisse angibt welcher Thread
den Monitor erhält.
boolean sync
Variable die angibt ob eine Synchronisation durch Ereignisse stattfinden soll.
99
5 Beispiel Applet
Methoden:
public void set(boolean syncronisieren,int anzahl)
Methode zum Starten und Beenden der Synchronisation durch Ereignisse.
public synchronized void put1 (String ausgabe)
Synchronisierte Methode für die Ausgabe von Thread 1
public synchronized void put2 (String ausgabe)
Synchronisierte Methode für die Ausgabe von Thread 2
public synchronized void put (String ausgabe)
Synchronisierte Methode für die Ausgabe von allgemeinen Informationen.
5.3.9 Die Klasse Steuerung.java
Über ein Objekt dieser Klasse können die Threads angehalten und zu einem beliebigen
Zeitpunkt weitergeführt werden.
Konstruktor:
public Steuerung(boolean wert)
Beim Erzeugen des Objektes muß der Anfangswert übergeben werden.
Attribute:
private boolean halt
Diese Variable zeigt an ob die Threads anhalten sollen oder nicht.
Methoden:
public void setanhalten ()
Methode um der Wert des Objektes auf true zu setzen.
public synchronized void weiter()
Methode um den Wert des Objektes auf false zu setzen.
public synchronized void anhalten ()
Methode bei dessen Ausführung ein Thread angehalten wird, wenn das
"Steuerugsobjekt" true ist.
100
5 Beispiel Applet
5.3.10 Die Klasse Syncprio.java
Ob eine Synchronisation durch die Priorität stattfindet, wird durch ein Objekt dieser Klasse
angezeigt. Die benötigten Schleifen um das System zu belasten gehören auch zu dieser
Klasse.
Konstruktor:
public Syncprio(boolean wert, int schleifen)
Die Schleifenzahl und ob eine Synchronisation durch die Priorität erfolgen soll
wird beim Erzeugen festgelegt.
Attribute:
private boolean sync
Zeigt an ob synchronisiert werden soll
private int schleifen
Gibt an wie oft die Threads die Methode "zeit()" aufrufen sollen.
Methoden:
public void setschleifen(int schleifen)
Methode mit der die Schleifenzahl verändert werden kann.
public int getschleifen()
Methode über die die Schleifenzahl erfragt werden kann.
public void setsync(boolean wert)
Methode über die festgelegt wird ob eine Synchronisation durch die Priorität
erfolgen soll oder nicht.
public boolean getsync()
Methode über die erfragt werden kann ob eine Synchronisation durch die
Priorität erfolgen soll.
public synchronized int zeit()
Methode um bei einer Synchronisation durch die Priorität das System zu
belasten.
101
6 Beispielprogramme
6 Beispielprogramme
Um die Beispielprogramme möglichst verständlich zu halten wurde in den meisten Fällen
ein Zähler verwendet, der seinen jeweiligen Zählerstand auf dem Bildschirm ausgibt bzw.
seinen Zählerstand einem anderen Prozess / Thread mitteilt.
Um eine Systembelastung zu erreichen wurden Schleifen in diese Zähler integriert, die
zusätzlich durchlaufen werden. Dies hat zur Folge, daß die Geschwindigkeit der
Programme stark von dem jeweiligem Rechner abhängig ist auf dem sie ausgeführt
werden. Sollte ein Programm zu schnell oder zu langsam auf dem jeweiligen Rechner
sein, muß einfach die Anzahl der Schleifendurchläufe anpaßt werden.
Die Beispielprogramme zu dieser Diplomarbeit wurden mit verschiedenen Werkzeugen
erstellt die im Anhang genauer erläutert werden.
102
6 Beispielprogramme
6.1 Beispielprogramme für Java
6.1.1 Counter
Das Programm Counter ist ein Zähler, der die Zahlen 0 bis 10 auf dem Bildschirm ausgibt.
Source-Code Counter (Dateiname: Counter.java):
import java.io.*;
public class Counter {
public static void main(String[] args) {
int read;
byte buffer[] = new byte[20];
System.out.println("Zum Starten und Beenden bitte Eingabetaste
druecken");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
// Wartet auf das Druecken der Eingabetaste
int lauf,lauf2,hilf;
for(hilf=0;hilf<=10;hilf++)
// Schleife fuer einen Zaehler
{
// Ausgabe des Zaehlers auf dem
System.out.println("Counter : "+hilf);
// Bildschirm
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=10000;lauf2++);
// Schleife um das
// System zu belasten
}
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
}
// Wartet auf das Druecken der Eingabetaste
}
Bildschirmausgabe Counter:
103
6 Beispielprogramme
6.1.2 Thread
Das Programm Thread ist, wie das Programm Counter, ein Zähler. Der Unterschied
zwischen den beiden Programmen liegt darin, daß bei dem Programm Thread der
Programmcode für den Zähler als Thread programmiert wurde. Dadurch kann das
Programm Thread vom Anwender unterbrochen werden.
Source-Code Thread (Dateiname EinThread.java):
import java.io.*;
class CountingThread extends Thread {
// Klasse wird von Thread abgeleitet
CountingThread(){};
// Konstruktor der Klasse
public void run(){
// Programmcode der als Thread
int lauf,lauf2,hilf;
// abgearbeitet werden soll
for(hilf=0;hilf<=10;hilf++) // Schleife fuer einen Zaehler
{
System.out.println("CountingThread : "+hilf);
for(lauf=0;lauf<=10000;lauf++)
// Schleife um das
for(lauf2=0;lauf2<=10000;lauf2++);
// System zu belasten
}
}
}
class EinThread {
//Main-Methode
public static void main(String[] args) {
int read;
byte buffer[] = new byte[20];
System.out.println("Zum Starten und Beenden bitte Eingabetaste
druecken");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
System.out.println("Thread gestartet\n");
CountingThread thread1 = new CountingThread();
// Thread "thread1" wird
thread1.start();
// Thread "thread1" wird
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
thread1.stop();
// Thread "thread1" wird
}
// wenn er nicht beendet
erzeugt
gestartet
gestoppt,
ist.
}
104
6 Beispielprogramme
Bildschirmausgabe Thread:
105
6 Beispielprogramme
6.1.3 Zwei Threads
Bei diesem Programm wurden zwei Zähler als Threads programmiert. Beide Zähler laufen
völlig unabhängig von einander.
Source-Code Zwei Threads (Dateiname ZweiThreads.java):
import java.io.*;
class CountingThread extends Thread {
CountingThread(){};
// Klasse wird von Thread abgeleitet
public void run(){
int lauf,lauf2,hilf;
// Programmcode der als Thread
// abgearbeitet werden soll
for(hilf=0;hilf<=10;hilf++)
// Schleife fuer einen Zaehler
{
System.out.println("Thread 1 : "+hilf);
for(lauf=0;lauf<=10000;lauf++)
// Schleife um das System
for(lauf2=0;lauf2<=10000;lauf2++);
// zu belasten
}
}
}
class CountingThreadRunnable implements Runnable {
CountingThreadRunnable(){}; // Klasse mit dem Interface Runnable
public void run(){
int lauf,lauf2,hilf;
// Programmcode der als Thread
// abgearbeitet werden soll
for(hilf=0;hilf<=10;hilf++)
// Schleife fuer einen Zaehler
{
System.out.println("
Thread 2 : "+hilf);
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=6000;lauf2++);
// Schleife um das System
}
// zu belasten
}
}
class ZweiThreads {
//Main-Methode
public static void main(String[] args) {
int read;
byte buffer[] = new byte[20];
System.out.println("Zum Starten und Beenden bitte Eingabetaste
druecken");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
System.out.println("Threads gestartet\n");
106
6 Beispielprogramme
Runnable rthread = new CountingThreadRunnable();
// Runnable-Objekt wird erzeugt
Thread thread2 = new Thread(rthread);
// Thread "thread2" wird erzeugt
// und das Runnable-Objekt uebergeben
CountingThread thread1 = new CountingThread();
// Thread "thread1" wird erzeugt
thread1.start();
// Beide Threads werden gestartet
thread2.start();
System.out.println("Prioritaet Thread 1 :
"+thread1.getPriority());
System.out.println("Prioritaet Thread 2 :
"+thread2.getPriority()+"\n");
// Die Prioritaet der Threads wird
// ermittelt und ausgegeben.
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
thread1.stop();
thread2.stop();
// Beide Threads werden gestoppt,
}
// wenn sie nicht beendet sind.
}
Bildschirmausgabe Zwei Threads:
107
6 Beispielprogramme
6.1.4 Priorität
In diesem Programm werden zwei Zähler und eine Steuerung als Threads gestartet. Die
Steuerungseinheit erhält die höchste Priorität. Beide Threads besitzen am Anfang die
Standardpriorität (Prioritätswert = 5). Nach einigen Sekunden wird die Priorität der beiden
Threads geändert.
Die Priorität von Thread 1 wird auf 3 herabgesetzt und die Priorität von Thread 2 wird auf
7 erhöht.
Source-Code Priorität (Dateiname ZweiThreadsPrio.java):
import java.io.*;
class CountingThread extends Thread {
// Klasse wird von Thread abgeleitet
CountingThread(){};
public void run(){
// Programmcode der als Thread
int lauf,lauf2,hilf;
// abgearbeitet werden soll
for(hilf=0;hilf<=10;hilf++)
{
System.out.println("Thread 1 : "+hilf);
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=10000;lauf2++);
}
}
}
class CountingThreadRunnable implements Runnable {
// Klasse mit dem Interface Runnable.
CountingThreadRunnable(){};
public void run(){
// Programmcode der als Thread
int lauf,lauf2,hilf;
// abgearbeitet werden soll
for(hilf=0;hilf<=10;hilf++)
{
System.out.println("
Thread 2 : "+hilf);
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=8000;lauf2++);
}
}
}
class Steuerung extends Thread {
Thread thread1;
// Klasse zur Steuerung der Threads
Thread thread2;
// wird von Thread abgeleitet
Steuerung(Thread t1, Thread t2){// Threads, die gesteuert werden,
thread1 = t1;
// werden hier uebernommen.
thread2 = t2;
}
108
6 Beispielprogramme
public void run() {
System.out.println("Steuerung als Thread gestartet");
this.setPriority(Thread.MAX_PRIORITY);
// Prioritaet des Steuerthreads wird
// auf maximum gesetzt
System.out.println("Prioritaet des Steuerungsthreads erhoeht auf
"+this.getPriority()+"\n");
thread1.setPriority(Thread.NORM_PRIORITY);
thread2.setPriority(Thread.NORM_PRIORITY);
// Prioritaet der zu steuernden Threads wird
// auf NORM_PRIORITY ( Prio = 5 ) gesetzt
thread1.start();
// Threads werden gestartet
thread2.start();
System.out.println("Threads zum Zaehlen gestartet\n");
System.out.println("Prioritaet Thread 1 :
"+thread1.getPriority());
System.out.println("Prioritaet Thread 2 :
"+thread2.getPriority()+"\n");
// Prioritaet der Threads wird ausgegeben
try {sleep(3000);}
// Steuerthread wartet 3 Sekunden
catch(InterruptedException e){};
System.out.println("\nDie Prioritaet von beiden Threads wird
geaendert");
thread1.setPriority(Thread.NORM_PRIORITY-2);
thread2.setPriority(Thread.NORM_PRIORITY+2);
// Prioritaet der Threads wird um 2 gesenkt bzw. erhoeht
System.out.println("Prioritaet Thread 1 :
"+thread1.getPriority());
System.out.println("Prioritaet Thread 2 :
"+thread2.getPriority()+"\n");
}
// Prioritaet der Threads wird ausgegeben
}
class ZweiThreadsPrio {
// Main-Methode
public static void main(String[] args) {
int read;
byte buffer[] = new byte[20];
System.out.println("Zum Starten und Beenden bitte Eingabetaste
druecken");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
Runnable rthread = new CountingThreadRunnable();
// Runnable-Objekt wird erzeugt
Thread thread2 = new Thread(rthread);
// Thread "thread2" wird erzeugt
// und das Runnable-Objekt uebergeben
CountingThread thread1 = new CountingThread();
// Thread "thread1" wird erzeugt
109
6 Beispielprogramme
Steuerung steuer = new Steuerung (thread1,thread2);
// Steuerungsthread wird erzeugt und
steuer.start();
// gestartet. Die zu steuernden Threads
// werden beim Erzeugen uebergeben.
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
thread1.stop();
thread2.stop();
// Alle Threads werden gestoppt,
steuer.stop();
// wenn sie nicht beendet sind.
}
}
Bildschirmausgabe Priorität:
110
6 Beispielprogramme
6.1.5 Monitore
Bei diesem Programm werden zwei Threads getartet. Beide Threads benutzen die selbe
synchronisierte Methode eines Objektes. Obwohl diese Methode durch eine Schleife
"verlangsamt" wurde entsteht keine Überschneidung der Ausgabe. Erst wenn ein Thread
diese Methode vollständig beendet hat darf ein anderer Thread diese Methode für seine
Ausgabe verwenden.
Source-Code Monitore (Dateiname SyncMonitor.java):
import java.io.*;
class CountingThread extends Thread {
Ausgabe ausg;
// Klasse wird von Thread abgeleitet
public CountingThread(Ausgabe ausg){
this.ausg=ausg;
// Objekt zur Ausgabe wird uebernommen
}
public void run(){
int lauf,lauf2,hilf;
// Programmcode der als Thread
// abgearbeitet werden soll
for(hilf=0;hilf<=3;hilf++)
{
ausg.put("Thread 1 : "+hilf,1);
// Ausgabe des Zaehlers
// ueber das Ausgabe-Objekt
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=100;lauf2++);
}
}
}
class CountingThreadRunnable implements Runnable {
Ausgabe ausg;
// Klasse mit dem Interface Runnable
public CountingThreadRunnable(Ausgabe ausg){
this.ausg=ausg;
// Objekt zur Ausgabe wird uebernommen
}
public void run(){
int lauf,lauf2,hilf;
for(hilf=0;hilf<=3;hilf++)
{
ausg.put("
Thread 2 : "+hilf,2);
// Ausgabe des Zaehlers ueber das Ausgabe-Objekt
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=60;lauf2++);
}
}
}
111
6 Beispielprogramme
class Ausgabe { // Klasse Ausgabe: Ueber ein Objekt dieser Klasse
// soll die Ausgabe auf den Bildschirm erfolgen
private String buffer;
private int nummer;
public Ausgabe(){
}
public synchronized void put (String ausgabe,int nummer){
// Synchronisierte Methode ueber
// die eine Ausgabe erfolgen soll.
this.buffer=ausgabe;
this.nummer=nummer;
if(nummer==1)
System.out.println("Thread Nummer "+nummer+" beginnt die
synchronisierte Methode");
else
System.out.println("
Thread Nummer "+nummer+" beginnt
die synchronisierte Methode");
System.out.println(buffer);
for(int lauf=0;lauf<=10000;lauf++)
// Schleife um die Methode
for(int lauf2=0;lauf2<=10000;lauf2++); // zu verlangsamen
if(nummer==1)
System.out.println("Thread Nummer "+nummer+" beendet die
synchronisierte Methode\n");
else
System.out.println("
Thread Nummer "+nummer+" beendet
die synchronisierte Methode\n");
}
}
class SyncMonitor {
// Main-Methode
public static void main(String[] args) {
int read;
byte buffer[] = new byte[20];
Ausgabe ausg = new Ausgabe();
// Ausgabe-Objekt wird erzeugt
System.out.println("Zum Starten und Beenden bitte Eingabetaste
druecken");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
System.out.println("Threads gestartet\n");
Runnable rthread = new CountingThreadRunnable(ausg);
Thread thread2 = new Thread(rthread);
CountingThread thread1 = new CountingThread(ausg);
// Threads werden erzeugt und das Ausgabe-Objekt uebergeben
112
6 Beispielprogramme
thread1.start();
// Threads werden gestartet
thread2.start();
System.out.println("Prioritaet Thread 1 :
"+thread1.getPriority());
System.out.println("Prioritaet Thread 2 :
"+thread2.getPriority()+"\n");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
thread1.stop();
thread2.stop();
// Threads werden gestoppt,
}
// wenn sie nicht beendet sind
}
Bildschirmausgabe Monitore:
113
6 Beispielprogramme
6.1.6 Sperr-Objekt
Dieses Programm enthält zusätzlich zu den beiden Threads eine Ausgabeklasse und eine
Klasse mit dem Naman SteuerObj. Die Ausgabe der beiden Threads erfolgt über die
Methode eines Objektes der Klasse Ausgabe. Diese Methode besteht aus einem nicht
synchronisierten und einen synchronisierten Teil. Die Synchronisation des
synchronisierten Teiles wird mit Hilfe eines Sperr-Objektes, ein Objekt der Klasse
SteuerObj, synchronisiert.
Die Bildschirmausgabe ist auf den ersten Blick etwas verwirrend, bei genauerer
Betrachtung stellt man aber fest, daß die unsynchronisierten Teile der Ausgabe beliebig
ausgegeben werden. Die synchronisierten Teile sperren sich aber gegenseitig so, daß
eine synchronisierte Ausgabe nie von einer anderen synchronisierten Ausgabe
unterbrochen wird.
Source-Code Sperr-Objekt (Dateiname SyncObjekt.java):
import java.io.*;
class CountingThread extends Thread {
Ausgabe ausg;
// Klasse wird von Thread abgeleitet
public CountingThread(Ausgabe ausg){
this.ausg=ausg;
// Objekt zur Ausgabe wird uebernommen
}
public void run(){
int lauf,lauf2,hilf;
// Programmcode der als Thread
// abgearbeitet werden soll
for(hilf=0;hilf<=3;hilf++)
{
ausg.put("Thread 1 : "+hilf,1);
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=50;lauf2++);
}
// Ausgabe des Zaehlers
// ueber das Ausgabe-Objekt
// Schleife um das System
// zu belasten
}
}
114
6 Beispielprogramme
class CountingThreadRunnable implements Runnable {
Ausgabe ausg;
// Klasse mit dem Interface Runnable
public CountingThreadRunnable(Ausgabe ausg){
this.ausg=ausg;
// Objekt zur Ausgabe wird uebernommen
}
public void run(){
int lauf,lauf2,hilf;
for(hilf=0;hilf<=3;hilf++)
{
ausg.put("
Thread 2 : "+hilf,2); // Ausgabe des Zaehlers
// ueber das Ausgabe-Objekt
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=50;lauf2++);
// Schleife um das
}
// System zu belasten
}
}
class SteuerObj{}
// Klasse fuer das Synchronisations-Objekt
class Ausgabe { // Klasse Ausgabe: Ueber ein Objekt dieser Klasse
// soll die Ausgabe auf dem Bildschirm erfolgen.
private String buffer;
private int nummer;
SteuerObj steuerobj = new SteuerObj();
// Synchromisationsobjekt wird erstellt
public Ausgabe(){
}
public void put (String ausgabe,int nummer){
this.buffer=ausgabe;
this.nummer=nummer;
// Nicht synchronisierter Teil der Methode
if(nummer==1)
System.out.println("Thread Nummer "+nummer+" beginnt den
nebenlaeufigen Teil der Methode");
else
System.out.println("
Thread Nummer "+nummer+" beginnt den
nebenlaeufigen Teil der Methode");
for(int lauf=0;lauf<=10000;lauf++)
for(int lauf2=0;lauf2<=10000;lauf2++);
// Belastungsschleife
if(nummer==1)
System.out.println("Thread Nummer "+nummer+" beendet den
nebenlaeufigen Teil der Methode");
else
System.out.println("
Thread Nummer "+nummer+" beendet den
nebenlaeufigen Teil der Methode");
115
6 Beispielprogramme
// Synchronisierter Teil der Methode
synchronized (steuerobj){
// Synchronisationsobjekt
// wird abgefragt
if(nummer==1)
System.out.println("Thread Nummer "+nummer+" beginnt den
synchronisierten Teil der Methode");
else
System.out.println("
Thread Nummer "+nummer+" beginnt den
synchronisierten Teil der Methode");
System.out.println(ausgabe);
for(int lauf=0;lauf<=10000;lauf++)
for(int lauf2=0;lauf2<=30000;lauf2++);
// Belastungsschleife
if(nummer==1)
System.out.println("Thread Nummer "+nummer+" beendet den
synchronisierten Teil der Methode");
else
System.out.println("
Thread Nummer "+nummer+" beendet den
synchronisierten Teil der Methode");
}
}
}
class SyncObjekt {
// Main-Methode
public static void main(String[] args) {
int read;
byte buffer[] = new byte[20];
Ausgabe ausg = new Ausgabe();
// Ausgabe-Objekt wird erzeugt
System.out.println("Zum Starten und Beenden bitte Eingabetaste
druecken");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
System.out.println("Threads gestartet\n");
Runnable rthread = new CountingThreadRunnable(ausg);
Thread thread2 = new Thread(rthread);
CountingThread thread1 = new CountingThread(ausg);
// Threads werden erzeugt und das Ausgabe-Objekt uebergeben
thread1.start();
thread2.start();
// Threads werden gestartet
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
thread1.stop();
thread2.stop();
// Threads werden gestoppt,
}
// wenn sie nicht beendet sind
}
116
6 Beispielprogramme
Bildschirmausgabe Sperr-Objekt:
117
6 Beispielprogramme
6.1.7 Ereignis
Bei diesem Programm werden zwei Threads über ein Ereignis synchronisiert. Als Ereignis
dient die Ausgabe auf dem Bildschirm. Nachdem ein Thread 3 Ausgaben auf dem
Bildschirm ausgegeben hat, gibt er ein Signal, daß nun ein anderer Thread die Ausgabe
benutzen darf.
Source-Code Ereignis (Dateiname ZweiThreadsEreignisse.java):
import java.io.*;
class CountingThread extends Thread {
Ausgabe ausg;
// Klasse wird von Thread abgeleitet
public CountingThread(Ausgabe ausg){
this.ausg=ausg;
// Objekt zur Ausgabe wird uebernommen
}
public void run(){
int lauf,lauf2,hilf;
// Programmcode der als Thread
// abgearbeitet werden soll
for(hilf=0;hilf<=10;hilf++)
{
ausg.put1("Thread 1 : "+hilf);
// Ausgabe des Zaehlers
for(lauf=0;lauf<=10000;lauf++)
// ueber das Ausgabe-Objekt
for(lauf2=0;lauf2<=10000;lauf2++);
}
ausg.set(false);
ausg.put1("");
// Beenden der Synchronisierung, da dieser
}
// Thread beendet ist und so nur noch max. 1
}
// Thread auf das Ausgabe-Objekt zugreift
class CountingThreadRunnable implements Runnable {
Ausgabe ausg;
// Klasse mit dem Interface Runnable
public CountingThreadRunnable(Ausgabe ausg){
this.ausg=ausg;
// Objekt zur Ausgabe wird uebernommen
}
public void run(){
int lauf,lauf2,hilf;
for(hilf=0;hilf<=10;hilf++)
{
// Ausgabe des Zaehlers ueber
ausg.put2("
Thread 2 : "+hilf); // das Ausgabe-Objekt
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=6000;lauf2++);
}
ausg.set(false); // Beenden der Synchronisierung, da dieser
ausg.put1("");
// Thread beendet ist und so nur noch max. ein
}
// Thread auf das Ausgabe-Objekt zugreift
}
118
6 Beispielprogramme
class Ausgabe {
// Klasse Ausgabe: Ueber ein Objekt dieser Klasse
// soll die Ausgabe auf dem Bildschirm erfolgen.
private
private
private
boolean
String buffer;
int anzahl=0;
int soll=3;
//
th1 = true;
//
//
boolean sync = true; //
//
Anzahl der Ausgaben
Variable die angibt, welcher Thread
den Monitor erhaelt
Synchronisation wird auf
true initalisiert
public Ausgabe(){
}
public void set(boolean sync){
this.sync=sync;
}
// Methode ueber die eine
// synchronisierte Ausgabe
// beendet werden kann
public synchronized void put1 (String ausgabe){
// Synchronisierte Methode
if (sync)
// Stellt fest, ob synchronisiert werden soll
{
if (!th1)
// Wenn th1 == false muß "thread1" warten
try {wait();}
catch(InterruptedException e) {}
this.buffer=ausgabe;
System.out.println(buffer);
// Ausgabe auf Bildschirm
anzahl=anzahl+1;
if (anzahl>=soll)
// Anzahl der Ausgaben wird mit
{
// dem Sollwert verglichen
anzahl=0;
th1=false;
// Variable fuer den Monitor wird umgestellt
notify();
// Eventuell wartender Thread erhaelt die
}
// Benachrichtigung, daß er jetzt weiter
}
// arbeiten kann
else
{
this.buffer=ausgabe;
if(!buffer.equals(""))
System.out.println(buffer); // Ausgabe auf dem Bildschirm
notify();
// Eventuell wartender Thread erhaelt die
}
// Benachrichtigung, daß er jetzt weiter
}
// arbeiten kann
119
6 Beispielprogramme
public synchronized void put2 (String ausgabe){
// Synchronisierte Methode
if (sync)
// Stellt fest, ob synchronisiert werden soll
{
if (th1)
// Wenn th1 == true muß "thread2" warten
try {wait();}
catch(InterruptedException e) {}
this.buffer=ausgabe;
System.out.println(buffer);
anzahl=anzahl+1;
if (anzahl>=soll)
{
anzahl=0;
th1=true;
notify();
}
// Ausgabe auf dem Bildschirm
// Anzahl der Ausgaben wird mit
// dem Sollwert verglichen
//
//
//
//
Variable fuer den Monitor wird umgestellt
Eventuell wartender Thread erhaelt die
Benachrichtigung, daß er jetzt weiter
arbeiten kann
}
else
{
this.buffer=ausgabe;
if(!buffer.equals(""))
System.out.println(buffer);
notify();
// Eventuell wartender Thread erhaelt die
}
// Benachrichtigung, daß er jetzt weiter
}
// arbeiten kann
}
class ZweiThreadsEreignisse {
// Main-Methode
public static void main(String[] args) {
int read;
byte buffer[] = new byte[20];
Ausgabe ausg = new Ausgabe();
// Ausgabe-Objekt wird erzeugt
System.out.println("Zum Starten und Beenden bitte Eingabetaste
druecken");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
System.out.println("Threads gestartet\n");
Runnable rthread = new CountingThreadRunnable(ausg);
Thread thread2 = new Thread(rthread);
CountingThread thread1 = new CountingThread(ausg);
// Threads werden erzeugt und das Ausgabe-Objekt uebergeben
thread1.start();
// Threads werden gestartet
thread2.start();
System.out.println("Prioritaet Thread 1 :
"+thread1.getPriority());
System.out.println("Prioritaet Thread 2 :
"+thread2.getPriority()+"\n");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
thread1.stop();
thread2.stop();
// Beide Threads werden gestoppt,
}
// wenn sie nicht beendet sind
}
120
6 Beispielprogramme
Bildschirmausgabe Ereignis:
121
6 Beispielprogramme
6.1.8 Reihenfolge
Bei diesem Programm werden zwei Threads erzeugt und gestartet. Thread 2 beginnt
sofort mit seiner Ausführung. Thread 1 wartet bis Thread 2 beendet ist um dann ebenfalls
seinen Programmcode auszuführen.
Source-Code Reihenfolge (Dateiname AufEndeThread.java):
import java.io.*;
class CountingThread extends Thread {
CountingThread(){};
// Klasse wird von Thread abgeleitet
public void start(Thread thread){
try{thread.join();}
catch(InterruptedException e) {}
super.start();
}
//
//
//
//
Start-Methode damit der
Thread wartet bis der
uebergebene Thread
beendet ist
public void run(){
int lauf,lauf2,hilf;
for(hilf=0;hilf<=10;hilf++)
{
System.out.println("Thread 1 : "+hilf); // Ausgabe des Zaehlers
for(lauf=0;lauf<=10000;lauf++)
// auf dem Bildschirm
for(lauf2=0;lauf2<=10000;lauf2++);
}
}
}
class CountingThreadRunnable implements Runnable {
// Klasse mit dem Interface Runnable
CountingThreadRunnable(){};
public void run(){
int lauf,lauf2,hilf;
for(hilf=0;hilf<=10;hilf++)
{
System.out.println("
Thread 2 : "+hilf);
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=6000;lauf2++);
}
}
}
122
6 Beispielprogramme
class AufEndeThread {
// Main-Methode
public static void main(String[] args) {
int read;
byte buffer[] = new byte[20];
System.out.println("Zum Starten und Beenden bitte Eingabetaste
druecken");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
System.out.println("Threads gestartet\n");
Runnable rthread = new CountingThreadRunnable();
Thread thread2 = new Thread(rthread);
CountingThread thread1 = new CountingThread();
// Erzeugen der beiden Threads
System.out.println("Prioritaet Thread 1 :
"+thread1.getPriority());
System.out.println("Prioritaet Thread 2 :
"+thread2.getPriority()+"\n");
thread2.start();
// Thread, auf den gewartet werden
// soll, wird zuerst gestartet.
thread1.start(thread2);
//
//
//
//
Thread, der warten soll wird
gestartet. Ihm wird der Thread
uebergeben "thread2" auf den
er warten soll.
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
thread1.stop();
thread2.stop();
// Threads werden gestoppt,
}
// wenn sie nicht beendet sind
}
123
6 Beispielprogramme
Bildschirmausgabe Reihenfolge:
124
6 Beispielprogramme
6.1.9 Pipe
Die Erzeugung und Verwendung einer Pipe zur Kommunikation zwischen zwei Threads
soll in diesem Programm möglichst einfach erläutert werden.
Source-Code Pipe (Dateiname Pipe.java):
import java.io.*;
class CountingThread extends Thread {
PipedInputStream pipein;
// Klasse wird von Thread abgeleitet
public CountingThread(PipedInputStream pipein){ // Konstruktor
this.pipein = pipein;
// Stream Objekt zum Lesen
};
// der Pipe wird uebernommen
public void run(){
int lauf,lauf2,hilf;
int ende=0;
byte lese[]=new byte[1];
int ausgabe;
String umwandeln;
// Hilfsvariablen
//
//
//
//
Feld um das augelesene
Byte zu speichern
Hilfsvariablen zum
Umformen der Ausgabe
while(true)
{
try {ende=pipein.read(lese,0,1);}
// Pipe wird ausgelesen
catch (IOException e) {}
if(ende==-1) break;
// bis -1 zurueckgegeben wird
umwandeln=""+lese[0];
// Daten aus der Pipe
ausgabe = Byte.parseByte(umwandeln); // werden fuer die
// Ausgabe umgewandelt
System.out.println("Thread 1 : "+ausgabe+" aus der Pipe
ausgelesen");
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=100;lauf2++);
}
try {pipein.close();}
catch(IOException e){}
// Schleife um das System
// zu belasten
// Schliesst den Stream
}
}
125
6 Beispielprogramme
class CountingThreadRunnable implements Runnable {
// Klasse mit dem Interface Runnable
PipedOutputStream pipeout;
public CountingThreadRunnable(PipedOutputStream pipeout){
// Konstruktor
this.pipeout=pipeout;
// Stream Objekt zum Schreiben
};
// in die Pipe wird uebernommen
public void run(){
int lauf,lauf2,hilf;
String umwandeln;
byte schreibe[] = new byte[1] ;
for(hilf=0;hilf<=10;hilf++)
{
// 10 mal wird eine Zahl in
// die Pipe geschrieben
umwandeln=""+hilf;
// Zahl wird zum
schreibe[0]=Byte.parseByte(umwandeln); // Schreiben umgewandelt
try {pipeout.write(schreibe,0,1);}
catch(IOException e){}
System.out.println("
// Daten werden in die
// Pipe geschrieben
Thread 2 : "+hilf+" in die
Pipe geschrieben");
for(lauf=0;lauf<=10000;lauf++)
for(lauf2=0;lauf2<=90;lauf2++);
// Schleife um das System
// zu belasten
try {pipeout.close();}
catch(IOException e){}
// Schliesst den Stream
}
}
}
126
6 Beispielprogramme
class Pipe {
// Main-Methode
public static void main(String[] args) {
int read;
byte buffer[] = new byte[20];
System.out.println("Zum Starten und Beenden bitte Eingabetaste
druecken");
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
PipedInputStream pipein = new PipedInputStream();
PipedOutputStream pipeout = new PipedOutputStream();
// Erzeugen der Stream-Objekte zum Schreiben und Lesen
try {pipeout.connect(pipein);}
catch (IOException e) {}
// Kopplung der Streams
System.out.println("Threads gestartet\n");
Runnable rthread = new CountingThreadRunnable(pipeout);
Thread thread2 = new Thread(rthread);
CountingThread thread1 = new CountingThread(pipein);
// Erzeugung der Threads mit Uebergabe der Stream-Objekte
thread1.start();
thread2.start();
// Starten der Threads
try {read = System.in.read(buffer,0,20);}
catch (IOException e ){};
thread1.stop();
thread2.stop();
// Threads werden gestoppt,
}
// wenn sie nicht beendet sind
}
Bildschirmausgabe Pipe:
127
6 Beispielprogramme
6.2 Beispielprogramme für Windows NT
6.2.1 Counter
Das Programm Counter ist ein Zähler, der die Zahlen 0 bis 10 auf dem Bildschirm ausgibt.
Source-Code Counter (Dateiname: Counter.cpp):
#include <iostream.h>
#include <conio.h>
main ()
{
int ein;
int lauf,lauf2,hilf;
// Variable zum Starten des Programmes
// Hilfsvariablen fuer Schleife
clrscr();
cout << "\nZum Starten und Beenden bitte Taste druecken\n\n";
ein = getch();
for(hilf=0;hilf<=10;hilf++)
{
cout << "Counter : " << hilf << endl;
for (lauf=0;lauf<=100000;lauf++)
for(lauf2=0;lauf2<=1000;lauf2++);
// Ausgabe der
// Zaehlerschleife
// Schleife um das
// System zu belasten
}
ein = getch();
return 0 ;
}
Bildschirmausgabe Counter:
128
6 Beispielprogramme
6.2.2 Thread
Das Programm Thread ist, wie das Programm Counter, ein Zähler. Der Unterschied
zwischen den beiden Programmen liegt darin, daß bei dem Programm Thread der
Programmcode für den Zähler als Thread programmiert wurde. Dadurch kann das
Programm Thread vom Anwender unterbrochen werden.
Source-Code Thread (Dateiname Thread.cpp):
#include <windows.h>
#include <iostream.h>
#include <conio.h>
long WINAPI Print( long lParam )
// Funktion, die der
{
// Thread durchlaeuft
int zahl=0,lauf,lauf2;
while(zahl<=10)
{
cout << "Thread # " << lParam << " Zahl = " << zahl << endl;
zahl=zahl+1;
for (lauf=0;lauf<=100000;lauf++)
// Schleife um das
for(lauf2=0;lauf2<=1000;lauf2++);
// System zu belasten
}
return NO_ERROR;
}
main ()
{
int ein;
int prio;
unsigned long nThreadID;
// Variable zum Starten des Programmes
// Variable fuer Prioritaet
// ID Nummer
clrscr();
cout << "\nZum Starten und Beenden bitte Taste druecken\n\n";
ein = getch();
HANDLE hThread = CreateThread(NULL,
// Thread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Print,
( void*)1,
0,
&nThreadID);
prio = GetThreadPriority( hThread );
cout << "Prioritaet: " << prio << " relative Prioritaet"
" innerhalb des Prozesses\n" << endl;
ein = getch();
CloseHandle(hThread);
return 0;
}
// Thread-Handle wird geschlossen
129
6 Beispielprogramme
Bildschirmausgabe Thread:
130
6 Beispielprogramme
6.2.3 Zwei Threads
Bei diesem Programm wurden zwei Zähler als Threads programmiert. Beide Zähler laufen
völlig unabhängig von einander.
Source-Code Zwei Threads (Dateiname 2Threads.cpp):
#include <windows.h>
#include <iostream.h>
#include <conio.h>
long WINAPI Print( long lParam )
{
int zahl=0,lauf,lauf2;
// Funktion, die der
// 1. Thread durchlaeuft
while(zahl<=10)
{
cout << "Thread # " << lParam << " Zahl = " << zahl << endl;
zahl=zahl+1;
for (lauf=0;lauf<=100000;lauf++)
// Schleife um Prozessor
for(lauf2=0;lauf2<=1000;lauf2++); // zu belasten
}
return NO_ERROR;
}
long WINAPI Print2( long lParam )
{
int zahl=0,lauf,lauf2;
// Funktion, die der
// 2. Thread durchlaeuft
while(zahl<=10)
{
cout << "\tThread # " << lParam << " Zahl = " << zahl << endl;
zahl=zahl+1;
for (lauf=0;lauf<=100000;lauf++)
// Schleife um Prozessor
for(lauf2=0;lauf2<=1500;lauf2++); // zu belasten
}
return NO_ERROR;
}
main ()
{
int ein;
int prio;
unsigned long nThreadID;
clrscr();
cout << "\nZum Starten und Beenden bitte Taste druecken\n\n";
ein = getch();
131
6 Beispielprogramme
HANDLE hThread = CreateThread(NULL,
// 1. Thread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Print,
( void*)1,
0,
&nThreadID);
HANDLE bThread = CreateThread(NULL,
// 2. Thread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Print2,
( void*)2,
0,
&nThreadID);
prio =
cout <<
<<
prio =
cout <<
<<
GetThreadPriority( hThread );
// Prioritaet wird
"Prioritaet Thread 1: " << prio // ermittelt und ausg.
" relative Prioritaet innerhalb des Prozesses\n" << endl;
GetThreadPriority( bThread );
"Prioritaet Thread 2: " << prio
" relative Prioritaet innerhalb des Prozesses\n" << endl;
ein = getch();
CloseHandle(hThread);
CloseHandle(bThread);
return 0 ;
}
// Thread-Handle hThread wird geschlossen
// Thread-Handle bThread wird geschlossen
Bildschirmausgabe Zwei Threads:
132
6 Beispielprogramme
6.2.4 Priorität
In diesem Programm werden zwei Zähler als Threads gestartet. Beide Threads besitzen
am Anfang die gleiche Priorität. Nach einiger Zeit ( 2-3 Sekunden ) wird die Priorität des
1. Threads um 2 herabgesetzt und die Priorität des 2. Threads um 1 erhöht.
Source-Code Priorität (Dateiname Priorität.cpp):
#include <windows.h>
#include <iostream.h>
#include <conio.h>
long WINAPI Print( long lParam )
{
int zahl=0,lauf,lauf2;
// Funktion, die der
// 1. Thread durchlaeuft
while(zahl<=10)
{
cout << "Thread # " << lParam << " Zahl = " << zahl << endl;
zahl=zahl+1;
for (lauf=0;lauf<=100000;lauf++)
// Schleife um Prozessor
for(lauf2=0;lauf2<=1000;lauf2++); // zu belasten
}
return NO_ERROR;
}
long WINAPI Print2( long lParam )
{
int zahl=0,lauf,lauf2;
// Funktion, die der
// 2. Thread durchlaeuft
while(zahl<=10)
{
cout << "\tThread # " << lParam << " Zahl = " << zahl << endl;
zahl=zahl+1;
for (lauf=0;lauf<=100000;lauf++)
// Schleife um Prozessor
for(lauf2=0;lauf2<=1500;lauf2++); // zu belasten
}
return NO_ERROR;
}
main ()
{
int ein;
int prio;
unsigned long nThreadID;
clrscr();
cout << "\nZum Starten und Beenden bitte Taste druecken\n\n";
ein = getch();
133
6 Beispielprogramme
HANDLE hThread = CreateThread (NULL,
// 1. Thread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Print,
( void*)1,
0,
&nThreadID);
HANDLE bThread = CreateThread (NULL,
// 2. Thread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Print2,
( void*)2,
0,
&nThreadID);
prio =
cout <<
<<
prio =
cout <<
<<
GetThreadPriority( hThread );
"Prioritaet Thread 1: " << prio
" relative Prioritaet innerhalb
GetThreadPriority( bThread );
"Prioritaet Thread 2: " << prio
" relative Prioritaet innerhalb
// Prioritaet wird
// ermittelt und ausg.
des Prozesses" << endl;
des Prozesses\n" << endl;
Sleep (3000);
cout << "\nDie Prioritaet von beiden Threads werden geaendert\n";
SetThreadPriority( hThread, THREAD_PRIORITY_LOWEST );
// Prioritaet vom Thread mit dem
// Handle hThread wird um 2 herabgestuft
SetThreadPriority( bThread, THREAD_PRIORITY_ABOVE_NORMAL );
// Prioritaet vom Thread mit dem
// Handle bThread wird um 1 heraufgesetzt
prio =
cout <<
<<
prio =
cout <<
<<
GetThreadPriority( hThread );
"Prioritaet Thread 1: " << prio
" relative Prioritaet innerhalb des Prozesses" << endl;
GetThreadPriority( bThread );
"Prioritaet Thread 2: " << prio
" relative Prioritaet innerhalb des Prozesses\n" << endl;
// neue Prioritaet der Threads wird
// ermittelt und ausgegeben
ein = getch();
CloseHandle(hThread);
CloseHandle(bThread);
return 0;
}
// Thread-Handle hThread wird geschlossen
// Thread-Handle bThread wird geschlossen
134
6 Beispielprogramme
Bildschirmausgabe Priorität:
135
6 Beispielprogramme
6.2.5 Ereignis
Bei diesem Programm werden zwei Threads über ein Ereignis synchronisiert. Als Ereignis
dient die Ausgabe auf dem Bildschirm. Nachdem ein Thread 4 Ausgaben auf dem
Bildschirm ausgegeben hat, gibt er ein Signal, daß nun ein anderer Thread die Ausgabe
benutzen darf.
Source-Code Ereignis (Dateiname Ereignis.cpp):
#include <windows.h>
#include <iostream.h>
#include <conio.h>
long WINAPI Print( long lParam )
{
int zahl=0,lauf,lauf2,ereignisse=0;
// Funktion, die der
// 1. Thread durchlaeuft
HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS,FALSE,"EventName");
// Handle auf das Ereignis
while(zahl<=10)
// wird geoeffnet
{
WaitForSingleObject(hEvent,INFINITE);
// Auf das Ereignis
// hEvent wird gewartet
while (ereignisse < 4 && zahl<=10)
{
cout << "Thread # " << lParam << " Zahl = " << zahl << endl;
zahl++;
ereignisse++;
// Ereignisse werden gezaehlt
for (lauf=0;lauf<=100000;lauf++)
// Schleife um Prozessor
for(lauf2=0;lauf2<=1000;lauf2++);
// zu belasten
}
SetEvent(hEvent);
// Ereignis wird auf signalisierend gesetzt
ereignisse=0;
// wenn 4 Bildschirmausgaben erfolgten
}
SetEvent(hEvent);
// Ereignis wird auf signalisierend gesetzt
return NO_ERROR;
// wenn die Funktion beendet wird
}
136
6 Beispielprogramme
long WINAPI Print2( long lParam )
{
int zahl=0,lauf,lauf2,ereignisse=0;
// Funktion, die der
// 2. Thread durchlaeuft
HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS,FALSE,"EventName");
// Handle auf das Ereignis
while(zahl<=10)
// wird geoeffnet
{
if (zahl!=0)
// Hierdurch wird festgelegt, daß der
// 2. Thread mit der Ausgabe beginnt
WaitForSingleObject(hEvent,INFINITE); // Auf das Ereignis
// hEvent wird gewartet
while (ereignisse < 4 && zahl<=10)
{
cout << "\tThread # " << lParam << " Zahl = " << zahl << endl;
zahl++;
ereignisse++;
for (lauf=0;lauf<=100000;lauf++)
// Schleife um Prozessor
for(lauf2=0;lauf2<=1500;lauf2++); // zu belasten
}
SetEvent(hEvent); // Ereignis wird auf signalisierend gesetzt
ereignisse=0;
// wenn 4 Bildschirmausgaben erfolgten
}
SetEvent(hEvent);
// Ereignis wird auf signalisierend gesetzt
return NO_ERROR;
// wenn die Funktion beendet wird
}
main ()
{
int ein;
int prio;
unsigned long nThreadID;
clrscr();
cout << "\nZum Starten und Beenden bitte Taste druecken\n\n";
ein = getch();
HANDLE hEvent = CreateEvent (NULL,
// Ereignis wird erzeugt
FALSE,
FALSE,
"EventName");
HANDLE hThread = CreateThread(NULL,
// 1. Thread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Print,
( void*)1,
0,
&nThreadID);
HANDLE bThread = CreateThread(NULL,
// 2. Thread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Print2,
( void*)2,
0,
&nThreadID);
137
6 Beispielprogramme
prio =
cout <<
<<
prio =
cout <<
<<
GetThreadPriority( hThread );
// Prioritaet wird
"Prioritaet Thread # 1: " << prio // ermittelt und ausg.
" relative Prioritaet innerhalb des Prozesses" << endl;
GetThreadPriority( bThread );
"Prioritaet Thread # 2: " << prio
" relative Prioritaet innerhalb des Prozesses\n" << endl;
ein = getch();
CloseHandle(hEvent);
CloseHandle(hThread);
CloseHandle(bThread);
return 0;
}
// Handles werden geschlossen
Bildschirmausgabe Ereignis:
138
6 Beispielprogramme
6.2.6 Semaphor
Das Programm erzeugt einen Semaphor, der auf 3 initialisiert wird, einen kritischen
Bereich und 10 Threads. Alle 10 Threads versuchen nun den Semaphor zu
dekrementieren, was den ersten 3 Threads auch gelingt. Alle anderen Threads müssen
warten, bis ein anderer Thread den Semaphor inkrementiert. Der kritische Bereich wird
bei diesem Programm benötigt, damit der Wert des Semaphores, der durch eine einfache
Variable dargestellt wird, von einem Thread, verändert und ausgegeben werden kann
ohne das ein anderer Thread zwischenzeitlich diese Variable verändert.
Source-Code Semaphor (Dateiname Semaphor.cpp):
#include <windows.h>
#include <iostream.h>
#include <conio.h>
CRITICAL_SECTION csOutput;
int sem=3;
// Variable fuer den
// kritischen Bereich
// Variable um Zaehlerstand des Semaphors anzuzeigen
long WINAPI Print( long lParam )
{
// Funktion, die die
// Threads durchlaufen
HANDLE semaphor = OpenSemaphore(SEMAPHORE_ALL_ACCESS,
NULL,
"Semap");
// Handle fuer Semaphor wird geoeffnet
cout << "Kunde "<< lParam << " betritt den Laden" <<endl;
WaitForSingleObject(semaphor,INFINITE);
// Semaphor wird abgefragt und wenn moeglich herabgesetzt
__try
{
// Eintritt in den kritischen Bereich
EnterCriticalSection( &csOutput );
sem--;
cout << "Kunde "<< lParam << " wird bedient"
<< "
Semaphor: "<<sem<<endl;
}
__finally
{
LeaveCriticalSection( &csOutput );
}
// Verlassen des kritischen Bereiches
139
6 Beispielprogramme
Sleep((rand() %3000)+2000);
__try
{
// Eintritt in den kritischen Bereich
EnterCriticalSection( &csOutput );
sem++;
cout << "Kunde "<< lParam << " verlaesst den Laden"
<< "
Semaphor: "<<sem<<endl;
}
__finally
{
LeaveCriticalSection( &csOutput );
}
// Verlassen des kritischen Bereiches
ReleaseSemaphore(semaphor,1,NULL);
return NO_ERROR;
}
// Semaphor wird um 1 erhoeht
int main()
{
int ein;
unsigned long nThreadID;
clrscr();
// Variable fuer den kritichen Bereich
InitializeCriticalSection(&csOutput);
// wird initialisiert
cout << "\nZum Starten und Beenden bitte Taste druecken\n\n";
ein = getch();
HANDLE semaphor = CreateSemaphore (NULL, // Semaphor wird erzeugt
3,
3,
"Semap");
for( int cThread=0; cThread<10; cThread++)
{
// 10 Threads werden erzeugt
CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)Print,
(void*)cThread,
0,
&nThreadID);
}
ein = getch();
CloseHandle(semaphor); // Handle des Semaphors wird geschlossen
return 0;
}
140
6 Beispielprogramme
Bildschirmausgabe Semaphor:
141
6 Beispielprogramme
6.2.7 Philosophen Quelle [HaWi1997]
Das Problem der "speisende Philosophen" wird sehr häufig verwendet um die Problematik
einer Synchronisation darzustellen.
Dieses Programm verwendet Mutexe und kritische Bereiche um die Threads zu
synchronisieren.
Source-Code Philosophen (Dateiname Phiol.cpp):
#include <iostream.h>
#include <windows.h>
#include <time.h>
long
g_fDone = FALSE;
struct CPhilosopher
{
int
m_nID;
HANDLE m_hForks[2];
int
m_nMeals;
};
CRITICAL_SECTION csOutput;
// Variable des kritischen Bereiches
void SayEat( CPhilosopher* pPhilo )
{
__try
{
// Eintritt in den kritischen Bereich
EnterCriticalSection( &csOutput );
cout << "Philosopher " << pPhilo->m_nID << " eats a while"
<< endl;
}
__finally
{
// Verlassen des kritischen Bereiches
LeaveCriticalSection( &csOutput );
}
pPhilo->m_nMeals++;
}
void SayThink( int nPhilo )
{
__try
{
// Eintritt in den kritischen Bereich
EnterCriticalSection( &csOutput );
cout << "Philosopher " << nPhilo << " thinks a while"
<< endl;
}
__finally
{
// Verlassen des kritischen Bereiches
LeaveCriticalSection( &csOutput );
}
}
142
6 Beispielprogramme
void SayDone( CPhilosopher* pPhilo )
{
__try
{
// Eintritt in den kritischen Bereich
EnterCriticalSection( &csOutput );
cout << "Philosopher " << pPhilo->m_nID << " had "
<< pPhilo->m_nMeals << " meals.(burp)" << endl;
}
__finally
{
// Verlassen des kritischen Bereiches
LeaveCriticalSection( &csOutput );
}
}
long WINAPI WaitToEat( long lParam )
{
CPhilosopher* pPhilo = (CPhilosopher*)lParam;
while( g_fDone == FALSE )
{
WaitForMultipleObjects(2,
pPhilo->m_hForks,
TRUE,
INFINITE );
//
//
//
//
Thread wartet
bis beide
Gabeln
frei sind
SayEat( pPhilo );
Sleep( (rand() % 1000) + 1000 );
SayThink( pPhilo->m_nID );
ReleaseMutex(pPhilo->m_hForks[1]);
Sleep(0);
ReleaseMutex(pPhilo->m_hForks[0]);
Sleep( (rand() % 1000) + 1000 );
}
SayDone( pPhilo );
return 0;
}
//
//
//
//
Thread gibt rechte
Gabel zurueck
Thread gibt linke
Gabel zurueck
int main()
{
const int nMaxPhil = 3;
// Anzahl der Philosophen am Tisch
srand((unsigned)time(NULL));
HANDLE
CPhilosopher
unsigned long
rghFork[nMaxPhil];
rgPhilosophers[nMaxPhil];
nThread;
// kritischer Bereich
InitializeCriticalSection( &csOutput ); // wird initialisiert
143
6 Beispielprogramme
for( int nFork = 0; nFork < nMaxPhil; nFork++ )
rghFork[nFork] = CreateMutex(NULL,FALSE,NULL);
// Fuer jede Gabel wird ein Mutex erzeugt
for( int nPhilo = 0; nPhilo < nMaxPhil; nPhilo++ )
{
rgPhilosophers[nPhilo].m_nID = nPhilo;
rgPhilosophers[nPhilo].m_nMeals = 0;
rgPhilosophers[nPhilo].m_hForks[0] = rghFork[nPhilo];
if( nPhilo < nMaxPhil-1 )
rgPhilosophers[nPhilo].m_hForks[1] = rghFork[nPhilo+1];
else
rgPhilosophers[nPhilo].m_hForks[1] = rghFork[0];
HANDLE hThread = CreateThread(NULL,
// Threads werden erzeugt
0,
(LPTHREAD_START_ROUTINE)WaitToEat,
(void*)&rgPhilosophers[nPhilo],
0,
&nThread );
CloseHandle( hThread );
}
Sleep( 10000 );
g_fDone = TRUE;
Sleep( 8000 );
DeleteCriticalSection( &csOutput );
return 0;
}
// Handle der Threads
// werden geschlossen
// kritischer Bereich
// wird geloescht
Bildschirmausgabe Philosophen:
144
6 Beispielprogramme
6.2.8 Ereignis für zwei Prozesse
In Windows NT können nicht nur Threads mit der Hilfe von Ereignissen synchronisiert
werden sondern auch Prozesse. Die beiden Beispielprogramme Wait Event und Sig Event
sollen eine solche Synchronisation darstellen.
Source-Code Wait Event (Dateiname WaitEvent.cpp):
#include <iostream.h>
#include <windows.h>
int main()
{
// Ereignis wird erzeugt
HANDLE hEvent = CreateEvent( NULL, FALSE, FALSE, "WaitEvent" );
cout << "Warte auf SigEvent" << endl;
WaitForSingleObject( hEvent, INFINITE ); // Auf das Ereignis
// wird gewartet
cout << "Event handle gefunden" << endl;
CloseHandle( hEvent );
// Ereignis-Handle wird geschlossen
return 0;
}
Source-Code Sig Event (Dateiname SigEvent.cpp):
#include <iostream.h>
#include <windows.h>
int main()
{
// Oeffnet den Handle fuer das Ereignis
HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE,"WaitEvent");
cout << "Signalisiere WaitEvent-Handle" << endl;
SetEvent( hEvent );
return 0;
}
// Setzt das Ereignis auf signalisierend
145
6 Beispielprogramme
Bildschirmausgabe der Prozesse Wait Event und Sig Event:
146
6 Beispielprogramme
6.2.9 Unbenannte Pipe
Die Erzeugung und Verwendung einer unbenannten Pipe zur Kommunikation zwischen
zwei Threads soll in diesem Programm möglichst einfach erläutert werden.
Source-Code unbenannte Pipe (Dateiname unbPipe.cpp):
#include <windows.h>
#include <iostream.h>
#include <conio.h>
const int cBuffer = 255;
HANDLE hRead,hWrite;
// Buffergroesse der Pipe
// Handle zum Lesen, Schreiben
long WINAPI Put()
// Funktion, die der Schreibthread durchlaeuft
{
int zahl=1;
TCHAR Msg[cBuffer];
DWORD dwWritten;
while(zahl<=10)
{
itoa(zahl,Msg,10);
BOOL fWritten = WriteFile (hWrite,
// schreiben in die Pipe
Msg,
sizeof(Msg),
&dwWritten,
NULL );
if( fWritten == FALSE || dwWritten == 0 )
{
cout << "Fehler beim Schreiben" << endl ;
break;
}
else
cout << "
Der Wert: " <<Msg<<" wurde gesendet" <<endl;
Sleep((rand() %1000)+500);
zahl++;
}
return NO_ERROR;
}
147
6 Beispielprogramme
long WINAPI Get()
// Funktion, die der Lesethread durchlaeuft
{
TCHAR Buffer[cBuffer];
DWORD dwRead;
while(1)
{
BOOL fRead = ReadFile (hRead,
// Auslesen der Pipe
Buffer,
sizeof (Buffer),
&dwRead,
NULL );
if( fRead == FALSE || dwRead == 0 )
{
cout << "Fehler beim Lesen" << endl;
break;
}
cout << "
Der Wert: " << Buffer
<< " wurde ausgelesen " << endl;
Sleep((rand() %1000)+500);
}
return NO_ERROR;
}
int main()
{
int ein;
unsigned long nThreadID;
clrscr();
cout << "\nZum Starten und Beenden bitte Taste druecken\n";
ein = getch();
BOOL fCreated = CreatePipe (&hRead,
&hWrite,
NULL,
0);
// Pipe wird erzeugt
if( !fCreated )
cout << "Fehler beim Erstellen der Pipe" << endl;
else
cout << "Pipe wurde erstellt" << endl ;
Sleep(2000);
HANDLE bThread = CreateThread (NULL, //Schreibthread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Put,
( void*)1,
0,
&nThreadID);
CloseHandle(bThread);
148
6 Beispielprogramme
cout << "Thread zum schreiben in die Pipe gestartet " << endl;
Sleep(2000);
HANDLE hThread = CreateThread(NULL,
// Lesethread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Get,
(void*)2,
0,
&nThreadID);
CloseHandle(hThread);
cout << "Thread zum Auslesen der Pipe gestartet " << endl;
ein = getch();
CloseHandle(hWrite);
CloseHandle(hRead);
return 0;
}
// Schreib- und Lesehandle
// werden geschlossen
Bildschirmausgabe unbenannte Pipe:
149
6 Beispielprogramme
6.2.10 Benannte Pipe eine Richtung
Dieses Programm soll die Verwendung einer benannten Pipe zur Kommunikation
zwischen zwei Threads zeigen. In diesem Beispiel erfolgt die Kommunikation
unidirektional.
Source-Code benannte Pipe eine Richtung (Dateiname benPipe1R.cpp):
#include <windows.h>
#include <iostream.h>
#include <conio.h>
const int cBuffer = 255;
const int cTimeout = INFINITE;
const TCHAR Pipe[] = TEXT("\\\\.\\pipe\\Quote");
// Pipename
long WINAPI Put()
// Funktion, die der
{
// Schreibthread durchlaeuft
int zahl=1;
DWORD dwWritten;
TCHAR Msg[cBuffer];
HANDLE hPipe = CreateFile (Pipe,
// Pipe Handle wird geoeffnet
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
DWORD dwPipeState = PIPE_READMODE_MESSAGE;
BOOL fChangedState = SetNamedPipeHandleState (hPipe,
&dwPipeState,
NULL,
NULL );
// Pipe wird in Nachrichten-Pipe umgewandelt
if( fChangedState == FALSE )
{
cout << "Fehler beim Umformen der Pipe" << endl;
}
else
cout << "Byte-Pipe in Nachrichten-Pipe umgeformt" << endl;
150
6 Beispielprogramme
while(zahl<=10)
{
itoa(zahl,Msg,10);
BOOL fWritten = WriteFile (hPipe,
// Schreiben in die Pipe
Msg,
lstrlen(Msg) + 1,
&dwWritten,
NULL );
if( fWritten == FALSE || dwWritten == 0 )
{
cout << "Fehler beim Schreiben" << endl ;
break;
}
else
cout<<"
Der Wert: "<<Msg<<" wurde gesendet"<<endl;
Sleep((rand() %1000)+500);
zahl++;
}
CloseHandle(hPipe);
// Handle der Pipe wird geschlossen
return NO_ERROR;
}
long WINAPI Get( long lParam ) // Funktion, die der Lesethread
{
// durchlaeuft Pipe Handle
TCHAR Buffer[cBuffer];
// wird uebergeben
DWORD dwRead;
HANDLE hPipe =(HANDLE)lParam;
while(1)
{
BOOL fRead = ReadFile (hPipe,
// Auslesen der Pipe
Buffer,
cBuffer,
&dwRead,
NULL );
if( fRead == FALSE || dwRead == 0 )
{
cout << "Fehler beim Lesen" << endl;
break;
}
cout << "
Der Wert: " << Buffer
<< " wurde ausgelesen " << endl;
if(lstrcmpi(TEXT("10"),Buffer)==0)break;
Sleep((rand() %1000)+500);
}
CloseHandle(hPipe);
// Handle der Pipe wird geschlossen
return NO_ERROR;
}
151
6 Beispielprogramme
int main()
{
int ein;
unsigned long nThreadID;
clrscr();
cout << "\nZum Starten und Beenden bitte Taste druecken\n";
ein = getch();
HANDLE hPipe = CreateNamedPipe (Pipe, // ben. Pipe wird erzeugt
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE |
PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
cBuffer,
cBuffer,
cTimeout,
NULL );
if( hPipe == INVALID_HANDLE_VALUE )
cout << "Fehler beim Erstellen der Pipe" << endl;
else
cout << "Pipe wurde erstellt" << endl;
Sleep(2000);
HANDLE bThread = CreateThread(NULL, // Schreibthread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Put,
( void*)1,
0,
&nThreadID);
CloseHandle(bThread);
cout << "Thread zum Schreiben in die Pipe gestartet " << endl;
Sleep(2000);
BOOL connected = ConnectNamedPipe( hPipe, NULL );
if(connected || GetLastError() == ERROR_PIPE_CONNECTED )
{
HANDLE hThread = CreateThread(NULL,
// Lesethread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Get,
(LPVOID)hPipe,
0,
&nThreadID);
CloseHandle(hThread);
cout << "Thread zum Auslesen der Pipe gestartet " << endl;
}
ein = getch();
CloseHandle(hPipe);
// Handle der Pipe wird geschlossen
return 0;
}
152
6 Beispielprogramme
Bildschirmausgabe benannte Pipe eine Richtung:
153
6 Beispielprogramme
6.2.11 Benannte Pipe zwei Richtungen
Dieses Programm soll die Verwendung einer benannten Pipe zur Kommunikation
zwischen zwei Threads zeigen. In diesem Beispiel erfolgt die Kommunikation
bidirektional.
Source-Code benannte Pipe zwei Richtungen (Dateiname benPipe2R.cpp):
#include <windows.h>
#include <iostream.h>
#include <conio.h>
const int cBuffer = 256;
const int cTimeout = INFINITE;
const TCHAR Pipe[] = TEXT("\\\\.\\pipe\\Quote"); // Name der Pipe
long WINAPI Put1()
// Funktion, die der 1. Thread durchlaeuft
{
int zahl=1;
DWORD dwWritten,dwRead;
TCHAR Msg[cBuffer];
TCHAR Buffer[cBuffer];
HANDLE hPipe = CreateFile (Pipe,
// Pipe Handle wird geoeffnet
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
DWORD dwPipeState = PIPE_READMODE_MESSAGE;
BOOL fChangedState = SetNamedPipeHandleState (hPipe,
&dwPipeState,
NULL,
NULL );
// Pipe wird in Nachrichten-Pipe umgewandelt
if( fChangedState == FALSE )
{
cout << "Fehler beim Umformen der Pipe" << endl;
}
else
cout << "Pipe umgeformt" << endl;
Sleep(100);
154
6 Beispielprogramme
while(1)
{
if(zahl<=10)
{
itoa(zahl,Msg,10);
BOOL fWritten = WriteFile (hPipe,
// Schreiben der Zahl
Msg,
// in die Pipe
lstrlen(Msg) + 1,
&dwWritten,
NULL );
if( fWritten == FALSE || dwWritten == 0 )
{
cout << "Fehler beim Schreiben" << endl;
break;
}
else
cout << "Sender
Der Wert: " << Msg << " wurde gesendet"
<< endl;
zahl++;
Sleep(500);
}
BOOL fRead = ReadFile (hPipe,
Buffer,
cBuffer,
&dwRead,
NULL );
// Lesen der Bestaetigung
// aus der Pipe
if( fRead == FALSE || dwRead == 0 )
{
cout << "Fehler beim Lesen" <<endl;
break;
}
else
{
cout << "Sender
" << Buffer <<" erhalten\n" << endl;
if(lstrcmpi(TEXT("Bestaetigung fuer Wert: 10"),Buffer)==0)
break;
}
}
CloseHandle(hPipe);
// Handle der Pipe wird geschlossen
return NO_ERROR;
}
155
6 Beispielprogramme
long WINAPI Get1( long lParam )
{
TCHAR Buffer[cBuffer];
TCHAR Msg[cBuffer];
DWORD dwRead,dwWritten;
HANDLE hPipe =(HANDLE)lParam;
while(1)
{
BOOL fRead = ReadFile (hPipe,
Buffer,
cBuffer,
&dwRead,
NULL );
// Funktion, die der 2. Thread
// durchlaeuft Pipe Handle
// wird uebergeben
// Pipe wird ausgelesen
cout << "Empfaenger
Der Wert: " << Buffer
<< " wurde empfangen / schicke Bestaetigung" << endl;
if( fRead == FALSE || dwRead == 0 )
{
cout <<"Fehler beim Lesen" << endl;
break;
}
else
{
strcpy(Msg,"Bestaetigung fuer Wert: ");
strcat(Msg,Buffer);
BOOL fWritten = WriteFile (hPipe,
// Bestaetigung wird
Msg,
// in die Pipe
lstrlen(Msg) + 1, // geschrieben
&dwWritten,
NULL );
if(lstrcmpi(TEXT("10"),Buffer)==0) break;
if( fWritten == FALSE || dwWritten == 0 )
{
cout << "Fehler beim Schreiben" << endl ;
break;
}
}
}
CloseHandle(hPipe);
// Handle der Pipe wird geschlossen
return NO_ERROR;
}
156
6 Beispielprogramme
int main()
{
int ein;
unsigned long nThreadID;
clrscr();
cout << "\nZum Starten und Beenden bitte Taste druecken\n";
ein = getch();
HANDLE hPipe = CreateNamedPipe (Pipe,
// ben. Pipe wird erzeugt
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE |
PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
cBuffer,
cBuffer,
cTimeout,
NULL );
if( hPipe == INVALID_HANDLE_VALUE )
cout << "Fehler beim Erstellen der Pipe" << endl;
else
cout << "Pipe wurde erstellt" << endl ;
Sleep(2000);
HANDLE bThread = CreateThread (NULL,
// 1. Thread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Put1,
( void*)1,
0,
&nThreadID);
CloseHandle(bThread);
cout<<"Thread zum Schreiben in die Pipe gestartet "<<endl;
BOOL connected = ConnectNamedPipe( hPipe, NULL );
if(connected || GetLastError() == ERROR_PIPE_CONNECTED )
{
HANDLE hThread = CreateThread (NULL,
// 2. Thread wird erzeugt
0,
(LPTHREAD_START_ROUTINE)Get1,
(LPVOID)hPipe,
0,
&nThreadID);
CloseHandle(hThread);
cout<<"Thread zum Auslesen der Pipe gestartet \n"<<endl;
}
ein = getch();
CloseHandle(hPipe);
// Handle der Pipe wird geschlossen
return 0;
}
157
6 Beispielprogramme
Bildschirmausgabe benannte Pipe zwei Richtungen:
158
6 Beispielprogramme
6.2.12 Benannte Pipe für zwei Prozesse
In Windows NT können benannte Pipes auch für die Kommunikation zwischen Prozesse
eingesetzt werden. Dies soll durch die beiden Programme benPipeE und benPipeS
gezeigt werden.
Source-Code benannte Pipe Empfänger (Dateiname benPipeE.cpp):
#include <windows.h>
#include <iostream.h>
#include <conio.h>
const int cBuffer = 256;
const int cTimeout = INFINITE;
const TCHAR Pipe[] = TEXT("\\\\.\\pipe\\Quote");
long WINAPI Print( long lParam )
{
TCHAR Buffer[cBuffer];
DWORD dwRead;
HANDLE hPipe =(HANDLE)lParam;
while(1)
{
BOOL fRead = ReadFile (hPipe,
Buffer,
cBuffer,
&dwRead,
NULL );
// Pipename
// Pipe Handle wird uebergeben
// Pipe wird ausgelesen
if( fRead == FALSE || dwRead == 0 )
{
cout << "Fehler beim Lesen" <<endl;
break;
}
if( lstrcmpi("Quit",Buffer)==0) // Thread wird beendet, sobald
{
// "Quit" empfangen wird
cout << "Quit empfangen beende Thread zum Auslesen der Pipe"
<< endl;
CloseHandle(hPipe);
// Handle der Pipe wird geschlossen
break;
}
else
cout << "Der Wert: " << Buffer << " wurde ausgelesen " << endl;
Sleep((rand() %1000)+500);
}
return NO_ERROR;
}
159
6 Beispielprogramme
int main()
{
int ein;
unsigned long nThreadID;
clrscr();
cout << "Zum Starten und Beenden bitte Taste druecken\n\n";
ein = getch();
HANDLE hPipe = CreateNamedPipe (Pipe, // ben. Pipe wird erzeugt
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE |
PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
cBuffer,
cBuffer,
cTimeout,
NULL);
if( hPipe == INVALID_HANDLE_VALUE )
cout << "Fehler beim Erstellen der Pipe" << endl;
cout << "Pipe wurde erstellt" << endl ;
BOOL connected = ConnectNamedPipe( hPipe, NULL );
// Thread zum Auslesen der Pipe wird
// erzeugt wenn die Pipe geoeffnet wird
if(connected || GetLastError() == ERROR_PIPE_CONNECTED )
{
HANDLE hThread = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)Print,
(LPVOID)hPipe,
0,
&nThreadID);
CloseHandle(hThread);
cout << "Thread zum Auslesen der Pipe gestartet " << endl;
}
ein = getch();
CloseHandle(hPipe);
return 0;
}
// Handle der Pipe wird geschlossen
160
6 Beispielprogramme
Source-Code benannte Pipe Sender (Dateiname benPipeS.cpp):
#include <windows.h>
#include <iostream.h>
#include <conio.h>
int main()
{
int ein;
int zahl=1;
const TCHAR Pipe[] = TEXT("\\\\.\\pipe\\Quote"); // Name der Pipe
boolean proz=false;
DWORD dwWritten;
TCHAR Msg[10];
// Nachrichtenfeld
HANDLE hPipe;
clrscr();
cout << "Zum Starten und Beenden bitte Taste druecken\n" << endl;
ein = getch();
while(proz==false)
{
hPipe = CreateFile (Pipe,
// Pipe wird geoeffnet
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if(hPipe == INVALID_HANDLE_VALUE)
{
cout << "Keine Pipe vorhanden Empfaengerprozess starten"
<< endl;
Sleep(1000);
}
else
{
proz=true;
}
}
DWORD dwPipeState = PIPE_READMODE_MESSAGE;
BOOL fChangedState = SetNamedPipeHandleState (hPipe,
&dwPipeState,
NULL,
NULL );
// Pipe wird in eine Nachrichten-Pipe umgewandelt
if( fChangedState == FALSE )
cout << "Fehler beim Umformen der Pipe " << endl;
else
cout << "Pipe umgeformt" << endl;
161
6 Beispielprogramme
while(zahl<=11)
{
if (zahl==11)
strcpy(Msg,"Quit"); // Nach 10 Zahlen
else
itoa(zahl,Msg,10);
BOOL fWritten = WriteFile (hPipe,
Msg,
lstrlen(Msg)
&dwWritten,
NULL );
wird "Quit" uebermittelt
// Nachricht wird in
// die Pipe geschrieben
+ 1,
if( fWritten == FALSE || dwWritten == 0 )
{
cout << "Fehler beim Schreiben" << endl ;
break;
}
else
cout << "Der Wert: " << Msg << " wurde gesendet" << endl;
Sleep((rand() %1000)+500);
zahl++;
}
CloseHandle(hPipe);
ein = getch();
return 0;
}
//Handle der Pipe wird geschlossen
Bildschirmausgabe der Prozesse ben. Pipe Empfänger und ben. Pipe Sender:
162
6 Beispielprogramme
6.3 Beispielprogramme für Unix / Linux
6.3.1 Counter
Das Programm Counter ist ein einfacher Zähler, der die Zahlen 0 bis 10 auf dem
Bildschirm ausgibt.
Source-Code Counter (Dateiname Counter.c):
#include<stdio.h>
main()
{
int ein;
int lauf,lauf2,hilf;
/* Hilfsvariablen fuer Schleifen
*/
printf("\nZum Starten und Beenden bitte Eingabetaste
druecken\n\n");
scanf("%c",&ein);
for(hilf=0;hilf<=10;hilf++)
{
printf("Counter: %i\n",hilf);
for (lauf=0;lauf<=100000;lauf++)
for (lauf2=0;lauf2<=1000;lauf2++);
}
scanf("%c",&ein);
return 0;
}
/* Einfacher Zaehler */
/* Schleife um den */
/* Prozessor zu belasten */
Bildschirmausgabe Counter:
163
6 Beispielprogramme
6.3.2 Prozess
Das Programm Prozess ist, wie das Programm Counter, ein einfacher Zähler. Der
Unterschied zwischen den beiden Programmen liegt darin, daß bei dem Programm
Prozess der Programmcode für den Zähler als Sohnprozess programmiert wurde.
Dadurch kann das Programm Prozess vom Anwender unterbrochen werden.
Source-Code Prozess (Dateiname Prozess.c):
#include<stdio.h>
main()
{
int ein;
int lauf,lauf2,hilf;
int prozess_pid;
/* Hilfsvariablen fuer Schleifen
/* Variable um die PID des */
/* Sohnes zu speichern */
printf("\nZum Starten und Beenden bitte Eingabetaste
druecken\n\n");
scanf("%c",&ein);
*/
if((prozess_pid = fork())==0)
/* Sohnprozess wird erzeugt */
{
for(hilf=0;hilf<=10;hilf++)
/* Einfacher Zaehler */
{
printf("Prozess: %i\n",hilf);
for (lauf=0;lauf<=100000;lauf++)
/* Schleife um das */
for (lauf2=0;lauf2<=1000;lauf2++); /* System zu belasten */
}
exit(0);
}
scanf("%c",&ein);
kill(prozess_pid,9);
/* Sohnprozess wird terminiert, */
return 0;
/* wenn er nicht bereits beendet ist */
}
Bildschirmausgabe Prozess:
164
6 Beispielprogramme
6.3.3 Zwei Prozesse
Bei diesem Programm wurden zwei Zähler als Prozesse programmiert. Beide Zähler
laufen völlig unabhängig von einander.
Source-Code ZweiProzesse (Dateiname 2Prozesse.c):
#include<stdio.h>
main()
{
int ein;
int lauf,lauf2,hilf;
/* Hilfsvariablen fuer Schleifen */
int prozess1_pid,prozess2_pid;
/* Variable um die PID´s */
/* der Soehne zu speichern */
printf("\nZum Starten und Beenden bitte Eingabetaste
druecken\n\n");
scanf("%c",&ein);
if((prozess1_pid = fork())==0) /* 1. Sohnprozess wird erzeugt */
{
for(hilf=0;hilf<=10;hilf++)
/* Einfacher Zaehler */
{
printf("Prozess 1: %i\n",hilf);
for (lauf=0;lauf<=100000;lauf++)
/* Schleife um das */
for (lauf2=0;lauf2<=1000;lauf2++);
/* System zu belasten */
}
exit(0);
}
if((prozess2_pid = fork())==0) /* 2. Sohnprozess wird erzeugt */
{
for(hilf=0;hilf<=10;hilf++)
/* Einfacher Zaehler */
{
printf("
Prozess 2: %i\n",hilf);
for (lauf=0;lauf<=100000;lauf++)
/* Schleife um das */
for (lauf2=0;lauf2<=500;lauf2++);
/* System zu belasten */
}
exit(0);
}
scanf("%c",&ein);
kill(prozess1_pid,9);
/* Sohnprozesse werden terminiert */
kill(prozess2_pid,9);
/* wenn sie nicht bereits beendet sind */
return 0;
}
165
6 Beispielprogramme
Bildschirmausgabe Zwei Prozesse:
166
6 Beispielprogramme
6.3.4 Semaphor
Das Programm erzeugt einen Semaphor, der auf 3 initialisiert wird, und 10 Prozesse
(Sohnprozesse). Alle 10 Prozesse versuchen nun den Semaphor zu dekrementieren, was
den ersten 3 Prozesse auch gelingt. Alle anderen Prozesse müssen warten bis ein
anderer Prozess den Semaphor inkrementiert.
Bei der Arbeit mit einem Semaphor gibt es systemabhänige Unterschiede. Dieses
Programm wurde für Linux und HP UNIX (Betriebssystem der Nebsy) programmiert. Vor
dem Compelieren muß der entsprechende "define" Befehl gesetzt werden.
Source-Code Semaphor (Dateiname Semaphor.c):
/*#define _HPUX_SOURCE */
#define _Linux_SOURCE
/*
/*
/*
/*
An diese Stelle kann festgelegt */
werden ob das Programm fuer
*/
Linux oder HP UNIX compiliert
*/
werden soll */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<stdlib.h>
struct sembuf sem_p[1];
/* Strucktur fuer */
/* P-Operation auf Semaphor */
struct sembuf sem_v[1];
/* Strucktur fuer */
/* V-Operation auf Semaphor */
main()
{
int ein;
int lauf,lauf2,hilf;
int prozess_pid[10];
int anzahl;
int semid;
ushort initarray[1];
ushort outarray[1];
#ifdef _Linux_SOURCE
union semun para;
union semun para2;
para.array=initarray;
para2.array=outarray;
#endif
/*
/*
/*
/*
/*
/*
Feld um die PID´s der */
Sohn-Prozesse zu speichern */
Anzahl der Sohn-Prozesse */
ID der Semaphorengruppe */
Initialisierungsfeld */
Ausgabefeld */
/* Variablen zum Arbeiten unter Linux */
167
6 Beispielprogramme
initarray[0]=3;
semid =semget(IPC_PRIVATE,1,IPC_CREAT|0777);
/* Erzeugung einer Semaphorgruppe mit einem Semaphor */
#ifdef _Linux_SOURCE
semctl(semid,0,SETALL,para);
#else
/* Setzen des Semaphores auf 3 */
semctl(semid,0,SETALL,initarray);
#endif
sem_p[0].sem_num = 0;
sem_p[0].sem_op = -1;
sem_p[0].sem_flg = 0;
/* Vorbereitung der P-Operation */
sem_v[0].sem_num = 0;
sem_v[0].sem_op = 1;
sem_v[0].sem_flg = 0;
/* Vorbereitung der V-Operation */
printf("\nZum Starten und Beenden bitte Eingabetaste
druecken\n\n");
scanf("%c",&ein);
for (anzahl=0;anzahl<10;anzahl++) /* 10 Sohn-Prozesse */
{
/* werden erzeugt */
if((prozess_pid[anzahl]=fork())==0)
{
printf("Kunde %i betritt den Laden \n",anzahl);
semop(semid,sem_p,1);
/* Semaphor dekrementieren */
printf("Kunde %i wird bedient \n",anzahl);
sleep(5);
/* Prozess schlaeft 5 Sekunden */
printf("Kunde %i verlaesst den Laden\n",anzahl);
semop(semid,sem_v,1);
/* Semaphor inkrementieren */
exit(0);
/* Prozess terminiert
*/
}
}
scanf("%c",&ein);
for (anzahl=0;anzahl<10;anzahl++) /* terminiere die Sohn- */
kill(prozess_pid[anzahl],9);
/* Prozesse wenn diese nicht */
/* bereits terminiert sind */
#ifdef _Linux_SOURCE
semctl(semid,0,IPC_RMID,para);
/* Loeschen der */
#else
/* Semaphorengruppe in */
semctl(semid,0,IPC_RMID,initarray); /* Linux oder HP UNIX */
#endif
return 0;
}
168
6 Beispielprogramme
Bildschirmausgabe Semaphor:
169
6 Beispielprogramme
6.3.5 Signale
Bei diesem Programm wird die Reihenfolge, in der die beiden Sohnprozesse (Zähler)
abgearbeitet werden, durch die Verwendung von Signalen gesteuert.
Source-Code Signale (Dateiname Signale.c):
#include<stdio.h>
#include<signal.h>
void sighand()
{
signal(SIGUSR1,&sighand);
/* Signal Handler der beim
*/
/* Eintreffen eines bestimmten */
/* Signales ausgefuehrt wird
*/
/* Bindung des Signales SIGUSR1 */
/* an den Signal Handler sighand()*/
printf("Habe Signal SIGUSR1 erhalten\n");
}
main()
{
int ein;
int lauf,lauf2,hilf;
int vater_pid,prozess1_pid,prozess2_pid;
signal (SIGUSR1,&sighand);
/* Bindung des Signales SIGUSR1
*/
/* an den Signal Handler sighand() */
printf("\nZum Starten und Beenden bitte Eingabetaste
druecken\n\n");
scanf("%c",&ein);
if((prozess1_pid = fork())==0)
{
vater_pid=getppid();
/* 1. Sohnprozess wird erzeugt */
/* Sohnprozess erfragt */
/* die PID des Vaters */
for(hilf=0;hilf<=10;hilf++)
/* Einfacher Zaehler */
{
printf("Prozess 1: %i\n",hilf);
for (lauf=0;lauf<=1000;lauf++)
for (lauf2=0;lauf2<=10000;lauf2++);
}
kill(vater_pid,SIGUSR1);
/* Dem Vaterprozess wird */
/* das Signal SIGUSR1 gesendet */
exit(0);
}
/* 1. Sohnprozess terminiert */
170
6 Beispielprogramme
if((prozess2_pid = fork())==0)
{
pause();
/* 2. Sohnprozess wird erzeugt */
/* 2. Sohnprozess wartet
/* auf ein Signal */
*/
for(hilf=0;hilf<=10;hilf++)
/* Einfacher Zaehler */
{
printf("
Prozess 2: %i\n",hilf);
for(lauf=0;lauf<=1000;lauf++)
for(lauf2=0;lauf2<=10000;lauf2++);
}
exit(0);
/* 2. Sohnprozess terminiert */
}
pause();
/* Vaterprozess wartet auf ein Signal */
kill(prozess2_pid,SIGUSR1); /* Vaterprozess sendet das Signal */
scanf("%c",&ein);
/* SIGUSR1 an den 2. Sohnprozess */
return 0;
}
Bildschirmausgabe Signale:
171
6 Beispielprogramme
6.3.6 Unbenannte Pipe
Die Erzeugung und Verwendung einer unbenannten Pipe zur Kommunikation zwischen
zwei Prozessen, die eine Vater Sohn Beziehung zueinander haben, soll in diesem
Programm möglichst einfach erläutert werden.
Source-Code unbenannte Pipe (Dateiname unbPipe.c):
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
main()
{
int ein;
/* Hilfsvariable fuer Programmstart */
int hilf;
int schreibprozess_pid,leseprozess_pid;
char inbuffer[2];
/* Hilfsvariable zum Umformen */
char outbuffer[2];
/* und Uebermitteln der Daten */
int fd[2];
/* Dateideskriptoren fuer Leseende */
/* (fd[0]) und Schreibende (fd[1]) */
printf("\nZum Starten und Beenden bitte Eingabetaste
druecken\n\n");
scanf("%c",&ein);
pipe(fd);
/* unbenannte Pipe wird erzeugt */
if((schreibprozess_pid = fork())==0)
/* Schreibprozess wird */
{
/* erzeugt */
for(hilf=0;hilf<10;hilf++)
{
close(fd[0]);
/* Dateideskriptor fuer das */
/* Leseende wird geschlossen */
inbuffer[0]=48+hilf;
write(fd[1],inbuffer,2);
/* 2 Bytes werden in die */
/* Pipe geschrieben*/
printf("Schreibprozess: schreibe den Wert %c in die
Pipe\n",inbuffer[0]);
sleep(1);
}
exit(0);
}
172
6 Beispielprogramme
if((leseprozess_pid = fork())==0) /* Leseprozess wird erzeugt */
{
close(fd[1]);
/* Dateideskriptor fuer das Schreibende */
while(1)
/* wird geschlossen */
{
read(fd[0],outbuffer,2);
/* 2 Bytes werden aus der */
/* Pipe ausgelesen */
printf("
Leseprozess: lese den Wert %s aus
der Pipe\n",outbuffer);
sleep(2);
}
exit(0);
}
scanf("%c",&ein);
kill(schreibprozess_pid,9); /* Schreib- und Leseprozess
*/
kill(leseprozess_pid,9);
/* werden terminiert wenn sie */
/* nicht bereits beendet sind */
return 0;
}
Bildschirmausgabe unbenannte Pipe:
173
6 Beispielprogramme
6.3.7 Benannte Pipe für zwei getrennte Prozesse
In Unix / Linux können benannte Pipes auch für die Kommunikation zwischen Prozesse
eingesetzt werden, die nicht miteinander "verwandt" sind. Dies soll durch die beiden
Programme benanntePipeE und benanntePipeS gezeigt werden.
Source-Code benannte Pipe Empfänger (Dateiname benPipeE.c):
#include<stdio.h>
#include<fcntl.h>
main()
{
int ein;
/* Hilfsvariable fuer Programmstart */
int hilf;
char outbuffer[2];
/* Buffer zum Auslesen der Pipe */
int fd;
/* Dateideskriptor fuer Pipe */
int lese;
/* speichert die Anzahl der gelesenen Bytes */
printf("\nZum Starten bitte Eingabetaste druecken\n");
scanf("%c",&ein);
printf("Empfaengerprozess wurde gestartet\n\n");
do
{
fd = open("MY_PIPE",O_RDONLY); /* Oeffnen der Pipe zum Lesen */
if (fd==-1)
/* Dateideskriptor wird */
/* zurueckgegeben */
printf("Bitte Prozess zum Schreiben in die Pipe starten\n");
sleep(2);
} while (fd==-1);
while(1)
/* Endlosschleife zum Auslesen der Pipe */
{
lese=read(fd,outbuffer,2); /* 2 Bytes werden ausgelesen */
if (lese==0) break;
printf("Leseprozess: lese den Wert %c aus der
Pipe\n",outbuffer[0]);
sleep(2);
}
unlink("MY_PIPE");
/* benannte Pipe wird geloescht */
return 0;
}
174
6 Beispielprogramme
Source-Code benannte Pipe Sender (Dateiname benPipeS.c):
#include<stdio.h>
#include<fcntl.h>
main()
{
int ein;
int hilf;
char inbuffer[2];
int fd;
/* Hilfsvariable fuer Programmstart */
/* Buffer zum Schreiben in die Pipe */
/* Dateideskriptor fuer Pipe */
printf("\nZum Starten bitte Eingabetaste druecken\n");
scanf("%c",&ein);
printf("Sendeprozess wurde gestartet\n\n");
mkfifo("MY_PIPE",0666);
/* benannte Pipe wird erzeugt */
fd = open("MY_PIPE",O_WRONLY); /* Oeffnen der Pipe zum */
/* Schreiben Dateideskriptor */
/* wird zurueckgegeben */
for(hilf=0;hilf<10;hilf++)
{
inbuffer[0]=48+hilf;
write(fd,inbuffer,2);
/* Zaehler zum Schreiben */
/* in die Pipe */
/* 2 Bytes werden in die */
/* Pipe geschrieben */
printf("Schreibprozess: schreibe den Wert %c in die
Pipe\n",inbuffer[0]);
sleep(1);
}
return 0;
}
175
6 Beispielprogramme
Bildschirmausgabe benannte Pipe für zwei getrennte Prozesse:
Empfängerprozess:
Sendeprozess:
176
7 Abschlussbetrachtung
7 Abschlussbetrachtung
Diese Diplomarbeit ermöglicht einen Vergleich, wie die Steuerung, Synchronisation und
Kommunikation von Prozessen / Threads in den Betriebssystemen Windows NT, Unix /
Linux und der Programmiersprache Java implementiert ist.
Die benötigten Funktionen bzw. Methoden präzise und vollständig zu erläutern ohne
dabei zu sehr abzuschweifen ist der zentrale Punkt dieser Diplomarbeit.
Grundkenntnisse von der jeweiligen Umgebung werden vorausgesetzt, da sonst der
Umfang, um alle drei Umgebungen zu erläutern, den Rahmen einer einzelnen
Diplomarbeit sprengen würde.
Obwohl in dieser Diplomarbeit die Unterschiede der einzelnen Umgebungen zu sehen
sind, ist es sehr schwer zu sagen, welche Umgebung für die Arbeit mit Prozessen /
Threads am geeignetsten ist, da jede Umgebung ihre Vor-und Nachteile hat.
Meine persönliche Erfahrung bei der Arbeit mit diesen Umgebungen war allerdings, daß
die Auswahl an Fachbüchern für Unix / Linux bzw. Java sehr umfangreich ist. Bei der
Arbeit mit Windows NT ist die Auswahl an unabhänigen Büchern bzw. Internetquellen
sehr beschränkt.
177
8 Anhang
8 Anhang
8.1 Literaturverzeichnis
Titel, Autor
Java Programmierhandbuch
Stefan Middendorf
Reiner Singer
UNIX System V.4
Jürgen Gulbins
Karl Obermayr
[MiSi1999]
[GuOb1995]
Jahr
Verlag
1999
dpunkt.verlag
1995
Springer-Verlag
[Herold1994]
UNIX Grundlagen
Helmut Herold
1994
ADDISON-WESLEY
[Stevens1995]
Programmierung in der
Unix-Umgebung
W.Richard Stevens
1994
ADDISON-WESLEY
[Vogt2001]
Betriebssysteme
Carsten Vogt
2001
Spektrum
Akademischer Verlag
2001
ADDISON-WESLEY
1997
Markt&Technik
Buchverlag
[Hansen]
[HaWi1997]
Windows-Programmierung
mit C++
Henning Hansen
Windows NT 4 Programmierung
David Hamilton
Mickey Williams
8.2 Internetquellen
URL
zuletzt verfügbar
[www01]
http://www.boku.ac.at/javaeinf
November 2001
[www02]
http://www.boku.ac.at/htmleinf
November 2001
[www03]
http://www.spotlight.de
November 2001
[www04]
http://www.sun.de
November 2001
[www05]
http://www.borland.com
November 2001
[www06]
http://www.borland.com/bcppbuilder/freecompiler/
November 2001
[www07]
http://www.pdfzone.com
November 2001
178
8 Anhang
8.3 Werkzeuge
Werkzeug
Anwendung
Borland Turbo Debugger Version 5.5
C / C++ Compiler ( WindowsNT )
Borland C++ 5.0
C / C++ Enwicklungsumgebung
Borland JBuilder3 Trail Edition
Java - Entwicklungsumgebung
GNU C / C++ Compiler
C / C++ Compiler (SuSE Linux 6.1)
SmartDraw 5
Zeichen- und Designsoftware
Adobe Acrobat 4.0
Dokumentenverwaltung
Microsoft Word 97
Textverarbeitung
Anmerkung:
Einige Beispielprogramme für Windows NT wurden von der Borland C++
Entwicklungumgebung nicht compiliert.
Mit dem frei erhältlichen Turbo Debugger 5.5 [www06] werden alle Beispielprogramme für
Windows NT ohne Fehlermeldung compiliert. Um die Arbeit mit diesem Compiler zu
erleichtern habe ich eine Configurationsdatei mit einigen Parameter in das BinVerzeichnis des Turbo Debuggers eingefügt:
bcc32.cfg
-I"C:\BCC55\INCLUDE"
-L"C:\BCC55\lib;C:\BCC55\lib\PSDK"
-DWINVER=0x0400
-D_WIN32_WINNT=0x0400
//
//
//
//
Pfad fuer INCLUDE Dateien
Pfad fuer lib Dateien
Parameter um Windows NT
Programm zu compilieren
Mit dem Aufruf bcc32 c:\Source\xxx.cpp im Bin-Verzeichnis des Turbo Debuggers
wird ohne weitere Parameter ein Programm für Windows NT compiliert .
179
8 Anhang
8.4 Source-Code des Beispiel Applets
8.4.1 Die Klasse Applet1.java
package BeispielApplet;
import
import
import
import
import
import
java.awt.*;
java.awt.event.*;
java.applet.*;
javax.swing.*;
java.beans.*;
java.net.*;
// Klasse zur Initialisierung der Oberflaeche
// und Ausfuehrung der Befehle
public class Applet1 extends Applet implements
ActionListener,FocusListener{
boolean isStandalone = false;
// Auswahlbox fuer Synchronisationsarten
private CheckboxGroup myCheckboxGroup;
private Checkbox groupBox1,groupBox2,groupBox3,groupBox4;
private boolean sync=false;
private
private
private
private
// Variable fuer Synchronisation
// durch Ereignis
boolean syncjoin = false; // Variable zur Synchr. auf
// das Ende eines Threads
JButton jButton_Counter;
// startet den Counter
JButton jButton_Thread2;
// startet Thread 2
JButton jButton_Thread2stop; // stoppt Thread 2
JButton jButton_Thread1;
// startet Thread 1
JButton jButton_Thread1stop; // stoppt Thread 1
JButton jButton_2Threads;
// startet beide Threads
JButton jButton_Threadsstop; // anhalten und
// weiterfuehren aller Threads
JButton jButton_RESET;
// RESET Button
JButton jButton_Thread3;
// startet Thread 3
JButton jButton_Thread3stop; // stoppt Thread 3
ImageCanvas screen;
private
private
private
private
CountingThread thread1;
Runnable rthread,bthread;
Thread thread2, thread3;
int lauf = 0;
private
private
private
private
private
private
private
private
//
//
//
//
Objekt fuer Thread 1
Runnable Objekte
Objekte fuer Thread 2 u. 3
Laufvariable
private int[] thindex = new int [3];
// Thread Index um
// festzuhalten welcher Thread gestartet wurde
private String sleepthread1;
private String sleepthread2;
// Variable um sleep-Zeit zu
// speichern, wenn durch
// Prioritaet synchronisiert wird
180
8 Anhang
private Steuerung steuer = new Steuerung(false);
// Steuerung zum Anhalten der Threads
private Beenden stop2 = new Beenden(false);
private Beenden stop3 = new Beenden(false);
// stop2 ist ein Objekt der Klasse Beenden und dient dazu dem
// Thread 2 anzuzeigen, das er seine run() Methode beenden soll
// stop3 ist fuer Thread 3
private Syncprio syncprio = new Syncprio(false,1000);
// Über diese Objekt dient Synchronisation durch Prioritaet
private IntJTextField jTextFieldA[] = new IntJTextField[13];
// Feld fuer 13 Text Felder (Eingabefelder) wird erzeugt
JLabel
JLabel
JLabel
JLabel
JLabel
JLabel
JLabel
JLabel
JLabel
JLabel
JLabel
JLabel
JLabel
jLabel1 = new JLabel();
jLabel2 = new JLabel();
jLabel3 = new JLabel();
jLabel4 = new JLabel();
jLabel5 = new JLabel(); // Labels fuer Beschriftung
jLabel7 = new JLabel(); // der Oberflaeche
jLabel8 = new JLabel();
jLabel9 = new JLabel();
jLabel10 = new JLabel();
jLabel11 = new JLabel();
jLabel12 = new JLabel();
jLabel13 = new JLabel();
jLabel14 = new JLabel();
Box box1;
Box box2;
Box box3;
JButton
JButton
JButton
JButton
JButton
JButton
JPanel
JPanel
JPanel
JPanel
JPanel
JPanel
// Boxen fuer Oberflaeche
jButton1
jButton2
jButton3
jButton4
jButton5
jButton6
jPanel1
jPanel2
jPanel3
jPanel4
JPanel5
JPanel6
=
=
=
=
=
=
=
=
=
=
=
=
new
new
new
new
new
new
new
new
new
new
new
new
// Buttons um die Prioritaet zu aendern
JButton();
// Thread 1 ab
JButton();
// Thread 1 auf
JButton();
// Thread 2 ab
JButton();
// Thread 2 auf
JButton();
// bel. Thread ab
JButton();
// bel. Thread auf
JPanel();
JPanel();
JPanel();
JPanel();
JPanel();
JPanel();
// Panels fuer Oberflaeche
MeineTextArea textArea1 = new MeineTextArea();
GridLayout gridLayout1 = new GridLayout();
GridLayout gridLayout2 = new GridLayout();
// Ausgabefeld
181
8 Anhang
// Parameterwert holen
public String getParameter(String key, String def) {
return isStandalone ? System.getProperty(key, def) :
(getParameter(key) != null ? getParameter(key) : def);
}
// Das Applet konstruieren
public Applet1() {
}
// Das Applet initialisieren
public void init() {
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}
// Initialisierung der Komponente
private void jbInit() throws Exception {
for (int i=0; i<thindex.length; i++)
thindex[i]=0;
// Schleife um die Textfelder zu initialisieren. Es wird
// festgelegt welchen Wertebereich die Textfelder haben.
for(int i=0;i<=jTextFieldA.length-1;i++)
{
if (i==3||i==7||i==11)
jTextFieldA[i] = new IntJTextField(1,10);
else
{
if(i==12)
jTextFieldA[i] = new IntJTextField(0,9999);
else
jTextFieldA[i] = new IntJTextField(0,100000);
}
}
for (int i=0;i<=10;i++)
// Textfelder werden dem Focus
{
// und Action Listener zugefuehrt
jTextFieldA[i].addFocusListener(this);
jTextFieldA[i].addActionListener(this);
}
jTextFieldA[0].setText("0");
jTextFieldA[1].setText("50");
jTextFieldA[2].setText("300");
jTextFieldA[3].setText("4");
// Werte fuer Textfelder
jTextFieldA[4].setText("51");
// werden vorgegeben
jTextFieldA[5].setText("100");
jTextFieldA[6].setText("400");
jTextFieldA[7].setText("4");
jTextFieldA[8].setText("0");
jTextFieldA[9].setText("20");
jTextFieldA[10].setText("200");
182
8 Anhang
// Textfeld fuer die Eingabe der Schleifenzahl wird dem
jTextFieldA[11].setText("2"); // ActionListener zugefuehrt
jTextFieldA[11].setBounds(new Rectangle(285, 25, 20, 25));
jTextFieldA[11].addActionListener(new
Applet1_jTextFieldA11_actionAdapter(this));
// Textfeld fuer die Eingabe der Schleifenzahl wird dem
jTextFieldA[12].setText("100"); // ActionListener zugefuehrt
jTextFieldA[12].setBounds(new Rectangle(265, 50, 40, 25));
jTextFieldA[12].addActionListener(new
Applet1_jTextFieldA12_actionAdapter(this));
box1 = Box.createVerticalBox();
box1.setBounds(new Rectangle(225, 35, 120, 100));
box1.add(jTextFieldA[0], null);
box1.add(jTextFieldA[1], null);
// Grafische Oberflaeche
box1.add(jTextFieldA[2], null);
// wird initalisiert
box1.add(jTextFieldA[3], null);
box2 = Box.createVerticalBox();
box2.setBounds(new Rectangle(350, 35, 120, 100));
box2.add(jTextFieldA[4], null);
box2.add(jTextFieldA[5], null);
// Grafische Oberflaeche
box2.add(jTextFieldA[6], null);
// wird initalisiert
box2.add(jTextFieldA[7], null);
box3 = Box.createVerticalBox();
box3.setBounds(new Rectangle(100, 35, 120, 75));
box3.add(jTextFieldA[8], null);
box3.add(jTextFieldA[9], null);
// Grafische Oberflaeche
box3.add(jTextFieldA[10], null);
// wird initalisiert
// Buttons werden erzeugt und initialisiert
jButton_Counter = new JButton("Counter start");
jButton_Counter.addActionListener(this);
jButton_Counter.setBounds(new Rectangle(100, 140, 120, 25));
jButton_Thread2 = new JButton("Thread 2 start");
jButton_Thread2.addActionListener(this);
jButton_Thread2.setBounds(new Rectangle(0, 5, 120, 25));
jButton_Thread1 = new JButton("Thread 1 start");
jButton_Thread1.addActionListener(this);
jButton_Thread1.setBounds(new Rectangle(0, 5, 120, 25));
jButton_2Threads = new JButton("Thread 1 u. Thread 2
starten");
jButton_2Threads.addActionListener(this);
jButton_2Threads.setBounds(new Rectangle(35, 137, 262, 27));
jButton_Threadsstop = new JButton("Alle Threads anhalten");
jButton_Threadsstop.addActionListener(this);
jButton_Threadsstop.setBounds(new Rectangle(320, 460, 200,
25));
183
8 Anhang
jButton_RESET = new JButton("RESET");
jButton_RESET.addActionListener(this);
jButton_RESET.setBounds(new Rectangle(525, 460, 100, 25));
jButton_Thread3 = new JButton("Thread 3 start");
jButton_Thread3.addActionListener(this);
jButton_Thread3.setBounds(new Rectangle(0, 5, 120, 25));
jButton_Thread1stop=new JButton("Thread 1 stop");
jButton_Thread1stop.addActionListener(this);
jButton_Thread1stop.setBounds(new Rectangle(0, 35, 120, 25));
jButton_Thread2stop=new JButton("Thread 2 stop");
jButton_Thread2stop.addActionListener(this);
jButton_Thread2stop.setBounds(new Rectangle(0, 35, 120, 25));
jButton_Thread3stop=new JButton("Thread 3 stop");
jButton_Thread3stop.addActionListener(this);
jButton_Thread3stop.setBounds(new Rectangle(0, 35, 120, 25));
jButton1.setBounds(new Rectangle(65, 95, 55, 25));
jButton1.setFont(new java.awt.Font("Dialog", 0, 10));
jButton1.setText("ab");
jButton1.addActionListener(new
java.awt.event.ActionListener(){
public void actionPerformed(ActionEvent e){
jButton1_actionPerformed (e);
}});
jButton2.setBounds(new Rectangle(65, 65, 55, 25));
jButton2.setFont(new java.awt.Font("Dialog", 0, 10));
jButton2.setText("auf");
jButton2.addActionListener(new
java.awt.event.ActionListener(){
public void actionPerformed(ActionEvent e){
jButton2_actionPerformed (e);
}});
jButton3.setBounds(new Rectangle(65, 95, 55, 25));
jButton3.setFont(new java.awt.Font("Dialog", 0, 10));
jButton3.setText("ab");
jButton3.addActionListener(new
java.awt.event.ActionListener(){
public void actionPerformed(ActionEvent e){
jButton3_actionPerformed (e);
}});
jButton4.setBounds(new Rectangle(65, 65, 55, 25));
jButton4.setFont(new java.awt.Font("Dialog", 0, 10));
jButton4.setText("auf");
jButton4.addActionListener(new
java.awt.event.ActionListener(){
public void actionPerformed(ActionEvent e){
jButton4_actionPerformed (e);
}});
184
8 Anhang
jButton5.setBounds(new Rectangle(65, 95, 55, 25));
jButton5.setFont(new java.awt.Font("Dialog", 0, 10));
jButton5.setText("ab");
jButton5.addActionListener(new
java.awt.event.ActionListener(){
public void actionPerformed(ActionEvent e){
jButton5_actionPerformed (e);
}});
jButton6.setBounds(new Rectangle(65, 65, 55, 25));
jButton6.setFont(new java.awt.Font("Dialog", 0, 10));
jButton6.setText("auf");
jButton6.addActionListener(new
java.awt.event.ActionListener(){
public void actionPerformed(ActionEvent e){
jButton6_actionPerformed (e);
}});
// Labels werden initialisiert
jLabel1.setHorizontalAlignment(SwingConstants.RIGHT);
jLabel1.setText("Priorität 1 - 10 :");
jLabel1.setBounds(new Rectangle(5, 110, 90, 25));
jLabel2.setHorizontalAlignment(SwingConstants.RIGHT);
jLabel2.setText("von :");
jLabel2.setBounds(new Rectangle(5, 35, 90, 25));
jLabel3.setHorizontalAlignment(SwingConstants.RIGHT);
jLabel3.setText("bis :");
jLabel3.setBounds(new Rectangle(5, 60, 90, 25));
jLabel4.setHorizontalAlignment(SwingConstants.RIGHT);
jLabel4.setText("sleep / ms :");
jLabel4.setBounds(new Rectangle(5, 85, 90, 25));
jLabel5.setHorizontalAlignment(SwingConstants.CENTER);
jLabel5.setText("Synchronisieren?");
jLabel5.setBounds(new Rectangle(35, 5, 233, 17));
jLabel5.setFont(new java.awt.Font("Dialog", 0, 14));
jLabel7.setHorizontalAlignment(SwingConstants.CENTER);
jLabel7.setText("Thread 1");
jLabel8.setHorizontalAlignment(SwingConstants.CENTER);
jLabel8.setText("Counter");
jLabel9.setHorizontalAlignment(SwingConstants.CENTER);
jLabel9.setText("Thread 2");
jLabel10.setHorizontalAlignment(SwingConstants.CENTER);
jLabel10.setText("Thread 3");
jLabel11.setHorizontalAlignment(SwingConstants.RIGHT);
jLabel11.setText("Aktuelle Priorität :");
jLabel11.setBounds(new Rectangle(100, 202, 115, 35));
185
8 Anhang
jLabel12.setHorizontalAlignment(SwingConstants.CENTER);
jLabel12.setText("");
jLabel12.setBounds(new Rectangle(5, 69, 50, 35));
jLabel12.setFont(new java.awt.Font("Dialog", 0, 14));
jLabel13.setHorizontalAlignment(SwingConstants.CENTER);
jLabel13.setText("");
jLabel13.setBounds(new Rectangle(12, 69, 41, 34));
jLabel13.setFont(new java.awt.Font("Dialog", 0, 14));
jLabel14.setText("");
jLabel14.setBounds(new Rectangle(12, 69, 35, 34));
jLabel14.setHorizontalAlignment(SwingConstants.CENTER);
jLabel14.setFont(new java.awt.Font("Dialog", 0, 14));
myCheckboxGroup = new CheckboxGroup();
groupBox1 = new Checkbox("ja, mit Ereignis
Ausgaben 1-10 :",myCheckboxGroup,false);
groupBox2 = new Checkbox("ja, mit Priorität
Schleifen :",myCheckboxGroup,false);
groupBox3 = new Checkbox("ja, auf das Ende von Thread
2",myCheckboxGroup,false);
groupBox4 = new Checkbox("nein",myCheckboxGroup,true);
groupBox1.setBounds(new
groupBox2.setBounds(new
groupBox3.setBounds(new
groupBox4.setBounds(new
Rectangle(6,
Rectangle(6,
Rectangle(6,
Rectangle(6,
25, 265, 25));
50, 250, 25));
75, 233, 25));
100, 233, 25));
groupBox1.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(ItemEvent e) {
groupBox1_itemStateChanged(e);
}
});
groupBox2.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(ItemEvent e) {
groupBox2_itemStateChanged(e);
}
});
groupBox3.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(ItemEvent e) {
groupBox3_itemStateChanged(e);
}
});
groupBox4.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(ItemEvent e) {
groupBox4_itemStateChanged(e);
}
});
186
8 Anhang
jPanel1.setLayout(gridLayout1);
jPanel1.setBackground(SystemColor.menu);
jPanel1.setBounds(new Rectangle(95, 5, 500, 30));
jPanel1.add(jLabel8, null);
jPanel1.add(jLabel7, null);
jPanel1.add(jLabel9, null);
jPanel1.add(jLabel10, null);
jPanel2.setLayout(null);
jPanel2.setBackground(SystemColor.menu);
jPanel2.setBounds(new Rectangle(225, 135, 120, 125));
jPanel2.add(jButton_Thread1, null);
jPanel2.add(jLabel12, null);
jPanel2.add(jButton2, null);
jPanel2.add(jButton1, null);
jPanel2.add(jButton_Thread1stop, null);
jPanel3.setLayout(null);
jPanel3.setBackground(SystemColor.menu);
jPanel3.setBounds(new Rectangle(350, 135, 120, 125));
jPanel3.add(jLabel13, null);
jPanel3.add(jButton4, null);
jPanel3.add(jButton3, null);
jPanel3.add(jButton_Thread2, null);
jPanel3.add(jButton_Thread2stop, null);
jPanel4.setLayout(gridLayout2);
jPanel4.setBounds(new Rectangle(475, 35, 120, 100));
JPanel5.setLayout(null);
JPanel5.setBackground(SystemColor.menu);
JPanel5.setBounds(new Rectangle(475, 135, 120, 125));
JPanel5.add(jLabel14, null);
JPanel5.add(jButton_Thread3, null);
JPanel5.add(jButton_Thread3stop, null);
JPanel5.add(jButton6, null);
JPanel5.add(jButton5, null);
JPanel6.setLayout(null);
JPanel6.setBackground(SystemColor.menu);
JPanel6.setBorder(BorderFactory.createLineBorder
(Color.black));
JPanel6.setBounds(new Rectangle(310, 265, 320, 180));
JPanel6.add(jTextFieldA[11], null);
JPanel6.add(jLabel5, null);
JPanel6.add(groupBox1, null);
JPanel6.add(groupBox2, null);
JPanel6.add(groupBox3, null);
JPanel6.add(groupBox4, null);
JPanel6.add(jButton_2Threads, null);
JPanel6.add(jTextFieldA[12], null);
textArea1.setBackground(Color.white);
textArea1.setBounds(new Rectangle(5, 265, 300, 230));
187
8 Anhang
String imagebase = "bilder/ms";
String imagetype = "jpg";
// Pfad fuer Bilder
// Format der Bilder
Image images[] = new Image[18];
// Feld fuer 18 Bilder
// Medistracker wird erzeugt
MediaTracker tracker= new MediaTracker(this);
// Bilder werden dem Mediatracker uebermittelt
for (int i=0;i<18;i++)
{
//images[i] = getImage(this.getCodeBase(),
imagebase+(i+1)+"."+imagetype);
// Dieser Befehl wird benoetigt, wenn das
// Applet ueber das Internet ausgefuehrt wird.
images[i] = Toolkit.getDefaultToolkit().getImage(
"D:/Dipl/BeispielApplet/bilder/ms"+(i+1)+".jpg");
// Mit diesem Befehl kann das Applet
// im Appletviewer ausgefuehrt werden.
tracker.addImage(images[i],i);
}
for (int i=0;i<18;i++)
{
try{tracker.waitForID(i); } catch (InterruptedException e){}
}
// Objekt zur Bildausgabe
screen = new ImageCanvas(images,steuer,stop3,syncprio);
screen.setBackground(Color.white);
jPanel4.add(screen, null);
this.setBackground(SystemColor.menu);
this.setLayout(null);
this.add(box1, null);
this.add(box2, null);
this.add(box3, null);
this.add(jPanel1, null);
this.add(jPanel2, null);
this.add(jButton_Counter, null);
this.add(jLabel1, null);
this.add(jLabel4, null);
this.add(jLabel3, null);
this.add(jLabel2, null);
this.add(jPanel3, null);
this.add(jPanel4, null);
this.add(JPanel5, null);
this.add(jLabel11, null);
this.add(textArea1, null);
this.add(jButton_Threadsstop, null);
this.add(jButton_RESET, null);
this.add(JPanel6, null);
}
188
8 Anhang
// Applet-Information holen
public String getAppletInfo() {
return "Applet-Information";
}
// Parameter-Infos holen
public String[][] getParameterInfo() {
return null;
}
// Main-Methode
public static void main(String[] args) {
Applet1 applet = new Applet1();
applet.isStandalone = true;
Frame frame = new Frame();
frame.setTitle("Applet-Bereich");
frame.add(applet, BorderLayout.CENTER);
applet.init();
applet.start();
frame.setSize(640,530);
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
frame.setLocation((d.width - frame.getSize().width) / 2,
(d.height - frame.getSize().height) / 2);
frame.setVisible(true);
}
// Abfrage welcher Knopf gedrueckt wurde
public void actionPerformed(ActionEvent e) {
String s = e.getActionCommand();
if (s.equals("Counter start"))
{
Counter count = new Counter(textArea1);
if (test())
// Funktion die kontrolliert, ob alle Textfelder
// in ihrem Wertebereich sind
// Dem Zaehler werden Daten uebergeben und gestartet
count.setcounter(jTextFieldA[8].getValue(),
jTextFieldA[9].getValue(),jTextFieldA[10].getValue());
}
else
if (s.equals("Thread 1 start"))
{
thread1start();
// Funktion um Thread 1 zu erzeugen
}
else
if (s.equals("Thread 1 stop"))
{
if(thindex[0]==1)
thread1.meinstop();
// Thread 1 wird ueber die
// Methode meinstop gestoppt
jLabel12.setText(""); // Anzeige der Prioritaet
}
// wird zurueckgesetzt
else
189
8 Anhang
if (s.equals("Alle Threads anhalten"))
{
steuer.setanhalten(); // Steuerungsobjekt wird auf true gesetzt
textArea1.put("Alle Threads angehalten\n");
jButton_Threadsstop.setLabel("Alle Threads weiterführen");
}
else
if (s.equals("Alle Threads weiterführen"))
{
steuer.weiter();
// Steuerungsobjekt wird auf false gesetzt
textArea1.put("Alle Threads weiterführen\n");
jButton_Threadsstop.setLabel("Alle Threads anhalten");
}
else
if (s.equals("Thread 1 u. Thread 2 starten"))
{
setsync();
// Synchronisationswert wird uebermittelt
thread1start();
// Methode um den ersten Thread zu erzeugen
thread2start();
// Methode um den zweiten Thread zu erzeugen
if(syncjoin)
// Prüfung, ob eine Reihenfoge beim
{
// Abarbeiten der Threads vorliegt.
thread2.start();
// starten von Thread 2
thread1.joinset(thread2);
// Thread 1 wird so gestartet,
}
// dass er nach dem Ende von
}
// Thread 2 anfaengt.
else
if (s.equals("Thread 2 start"))
{
thread2start();
// Eigene Methode um Thread 2 zu erzeugen
}
else
if (s.equals("Thread 2 stop"))
{
stop2.set(true);
// Setzt das Objekt stop2 auf true
jLabel13.setText("");
// Anzeige der Prioritaet
}
// wird zurueckgesetzt
else
if (s.equals("RESET"))
{
textArea1.replaceText("",0,textArea1.getCaretPosition());
jLabel12.setText("");
jLabel13.setText("");
// Anzeigen werden zurueckgesetzt
jLabel14.setText("");
jButton_Threadsstop.setLabel("Alle Threads anhalten");
stop2.set(false);
stop3.set(false);
// Threads erhalten ueber ein Objekt
steuer.weiter();
// die Nachricht, dass sie stoppen sollen
if (thindex[0]!=0 && thread1.isAlive())
thread1.stop();
// Alle laufenden
if (thindex[1]!=0 && thread2.isAlive()) // Threads werden ohne
thread2.stop();
// das sie ihre run()
if (thindex[2]!=0 && thread3.isAlive()) // Methode beenden
thread3.stop();
// koennen, gestoppt.
}
190
8 Anhang
else
if (s.equals("Thread 3 start"))
{
// Es wird kontrolliert,
if (thindex[2]==1 && thread3.isAlive()) // ob Thread 3
{
// bereits laeuft.
textArea1.put("Thread 3 is Alive\n");
}
else
{
thread3 = new Thread(screen);
// Thread 3 wird erzeugt
thindex[2]=1;
stop3.set(false);
// Objekt stop3 wird auf false gesetzt
thread3.setPriority(1);
// Prioritaet von Thread 3 auf 1
thread3.start();
// Thread 3 wird gestartet
textArea1.put("Thread 3 gestartet\n");
String hilf2 = String.valueOf(thread3.getPriority());
jLabel14.setText(hilf2);
// Prioritaet wird ausgegeben
}
}
else
if (s.equals("Thread 3 stop"))
{
if (thread3.isAlive())
{
stop3.set(true);
// Objekt zum Beenden von Thread 3
textArea1.put("Thread 3 gestoppt\n"); // wird auf true gesetzt
jLabel14.setText(""); // Prioritaetsanzeige wird zurueckgesetzt
}
}
}
// Methode um Thread 1 zu erzeugen und ggf. zu starten
void thread1start(){
if (thindex[0]==1 && thread1.isAlive())
{
// Abfrage ob Thread 1 bereits laeuft
textArea1.put("Thread 1 is Alive\n");
}
else
{
// Erzeugung des Thread Objektes
thread1 = new CountingThread(syncprio,textArea1,
steuer,jLabel12);
if (test())
// Methode, die kontrolliert, ob alle
{
// Textfelder in ihrem Wertebereich sind.
if (syncprio.getsync()) // Abfrage ob ueber die Prioritaet
{
// synchronisiert werden soll.
thread1.setcountingthread(jTextFieldA[0].getValue(),
jTextFieldA[1].getValue(),1);
}
// Setzen der Daten fuer den Thread
else
{
thread1.setcountingthread(jTextFieldA[0].getValue(),
jTextFieldA[1].getValue(),jTextFieldA[2].getValue());
}
// Setzen der Daten fuer den Thread
191
8 Anhang
thread1.setPriority(jTextFieldA[3].getValue());
// Prioritaet des Threads wird gesetzt
String hilf2 = String.valueOf(thread1.getPriority()) ;
jLabel12.setText(hilf2);
// Ausgabe der Prioritaet
setsync();
// Synchronisation durch Ereignisse ?
if (!syncjoin)
// Wird keine Synchronisation der
thread1.start();
// Reihenfolge verlangt wird
thindex[0]=1;
// Thread 1 gestartet.
jButton_Threadsstop.setLabel("Alle Threads anhalten");
steuer.weiter();
// Objekt das anzeigt ob die Threads
}
// anhalten sollen wird auf false gesetzt
}
}
// Methode um Thread 2 zu erzeugen und ggf. zu starten
void thread2start(){
if (thindex[1] != 0 && thread2.isAlive())
{
textArea1.put("Thread 2 is Alive\n");
}
else
if (test())
// Textfelder in ihrem Wertebereich ?
{
if (syncprio.getsync())
// Synchr.n ueber die Prioritaet ?
{
rthread = new CountingThreadRunnable(jLabel13,syncprio,stop2,
steuer,textArea1,jTextFieldA[4].getValue(),
jTextFieldA[5].getValue(),1);
}
// Runnable Objekt wird erzeugt
else
// und Daten werden uebergeben
{
rthread = new CountingThreadRunnable(jLabel13,syncprio,stop2,
steuer,textArea1,jTextFieldA[4].getValue(),
jTextFieldA[5].getValue(),jTextFieldA[6].getValue());
}
thread2 = new Thread(rthread);
// Thread 2 wird erzeugt und das Runnable Objekt uebergeben
thread2.setPriority(jTextFieldA[7].getValue());
String hilf3 = String.valueOf(thread2.getPriority()) ;
jLabel13.setText(hilf3);
stop2.set(false);
// stop2 wird auf false gesetzt
setsync();
// Synchronisation durch Ereignisse ?
if (!syncjoin)
// Wird keine Synchronisation der
thread2.start();
// Reihenfolge verlangt
thindex[1]=1;
// wird Thread 2 gestartet.
jButton_Threadsstop.setLabel("Alle Threads anhalten");
steuer.weiter();
// Objekt das anzeigt ob die Threads
}
// anhalten sollen wird auf false gesetzt
}
192
8 Anhang
// Methode, die ausgefuehrt wird um die Prioritaet
// von Thread 1 herabzusetzen.
void jButton1_actionPerformed(ActionEvent e){
if (thindex[0] != 0 && thread1.isAlive())
if (thread1.getPriority()>1)
{
thread1.setPriority(thread1.getPriority()-1);
// Prioritaet wird um 1 herabgesetzt
String s = String.valueOf(thread1.getPriority()) ;
jLabel12.setText(s);
// Neue Prioritaet wird angezeigt
textArea1.put("Thread 1 neue Priorität : "+s+"\n");
}
}
// Methode, die ausgefuehrt wird um die Prioritaet
// von Thread 1 heraufzusetzen.
void jButton2_actionPerformed(ActionEvent e){
if (thindex[0] != 0 && thread1.isAlive())
if (thread1.getPriority()<10)
{
thread1.setPriority(thread1.getPriority()+1);
// Prioritaet wird um 1 erhoeht
String s = String.valueOf(thread1.getPriority()) ;
jLabel12.setText(s);
// Neue Prioritaet wird angezeigt
textArea1.put("Thread 1 neue Priorität : "+s+"\n");
}
}
// Methode, die ausgefuehrt wird um die Prioritaet
// von Thread 2 herabzusetzen
void jButton3_actionPerformed(ActionEvent e){
if (thindex[1] != 0 && thread2.isAlive())
if (thread2.getPriority()>1)
{
thread2.setPriority(thread2.getPriority()-1);
// Prioritaet wird um 1 herabgesetzt
String s = String.valueOf(thread2.getPriority()) ;
jLabel13.setText(s);
// Neue Prioritaet wird angezeigt
textArea1.put("Thread 2 neue Priorität : "+s+"\n");
}
}
// Methode, die ausgefuehrt wird um die Prioritaet
// von Thread 2 heraufzusetzen
void jButton4_actionPerformed(ActionEvent e){
if (thindex[1] != 0 && thread2.isAlive())
if (thread2.getPriority()<10)
{
thread2.setPriority(thread2.getPriority()+1);
// Prioritaet wird um 1 erhoeht
String s = String.valueOf(thread2.getPriority()) ;
jLabel13.setText(s);
// Neue Prioritaet wird angezeigt
textArea1.put("Thread 2 neue Priorität : "+s+"\n");
}
}
193
8 Anhang
// Methode, die ausgefuehrt wird um die Prioritaet
// von Thread 3 herabzusetzen
void jButton5_actionPerformed(ActionEvent e){
if (thindex[2] != 0 && thread3.isAlive())
if (thread3.getPriority()>1)
{
thread3.setPriority(thread3.getPriority()-1);
// Prioritaet wird um 1 herabgesetzt
String s = String.valueOf(thread3.getPriority()) ;
jLabel14.setText(s);
// Neue Prioritaet wird angezeigt
textArea1.put("Thread 3 neue Priorität : "+s+"\n");
}
}
// Methode, die ausgefuehrt wird um die Prioritaet
// von Thread 3 heraufzusetzen
void jButton6_actionPerformed(ActionEvent e){
if (thindex[2] != 0 && thread3.isAlive())
if (thread3.getPriority()<10)
{
thread3.setPriority(thread3.getPriority()+1);
// Prioritaet wird um 1 erhoeht
String s = String.valueOf(thread3.getPriority());
jLabel14.setText(s);
// Neue Prioritaet wird angezeigt
textArea1.put("Thread 3 neue Priorität : "+s+"\n");
}
}
public void focusLost(FocusEvent e){}
public void focusGained(FocusEvent e){
test();
// Textfelder in ihrem Wertebereich ?
}
// Methode, die kontrolliert, ob alle Textfelder
// in ihrem Wertebereich sind
public boolean test(){
for(int i=0;i<=jTextFieldA.length-1;i++)
{
if (i==2) i++;
if (i==6) i++;
if(jTextFieldA[i].isValid()==false)
{
jTextFieldA[i].requestFocus();
return false;
}
// Ist die Eingabe eines Textfeldes nicht
}
// gueltig so erhaelt dieses Textfeld den Focus
// und es wird false zurueckgegeben.
if(!syncprio.getsync()&&jTextFieldA[2].isValid()==false)
{
jTextFieldA[2].requestFocus();
return false;
// Hier wird geprueft ob
}
// durch die Prioritaet
// synchronisiert werden soll.
194
8 Anhang
if(!syncprio.getsync()&&jTextFieldA[6].isValid()==false)
{
// Wird durch die Prioritaet
jTextFieldA[6].requestFocus();// synchronisiert stehen in
return false;
// diesen Felder keine Zahlen.
}
if (jTextFieldA[1].getValue() < jTextFieldA[0].getValue())
{
jTextFieldA[1].requestFocus();
jTextFieldA[1].setText("keine pos. Differenz");
jTextFieldA[1].selectAll();
return false;
// Es wird kontrolliert ob eine positive
}
// Differenz vorliegt.
if (jTextFieldA[9].getValue() < jTextFieldA[8].getValue())
{
jTextFieldA[9].requestFocus();
jTextFieldA[9].setText("keine pos. Differenz");
jTextFieldA[9].selectAll();
return false;
}
if (jTextFieldA[5].getValue() < jTextFieldA[4].getValue())
{
jTextFieldA[5].requestFocus();
jTextFieldA[5].setText("keine pos. Differenz");
jTextFieldA[5].selectAll();
return false;
}
return true;
}
// Diese Methode stellt fest ob die Ausgabe
// synchronisiert werden soll.
public void setsync(){
if(jTextFieldA[11].isValid()==false)
{
jTextFieldA[11].setText("2");;
}
// Uebergibt die Anzahl der Ausgaben
else
// durch die synchronisiert werden soll.
textArea1.set(sync,jTextFieldA[11].getValue());
}
// Methode wird ausgefuehrt, wenn
// durch Ereignisse synchronisiert werden soll
void groupBox1_itemStateChanged(ItemEvent e) {
if(thindex[0]==1)
// Wenn Thread 1 laeuft
thread1.meinstop();
// wird dieser beendet.
stop2.set(true);
// Objekt fuer Thread 2
// wird auf beenden gesetzt
if (syncprio.getsync())
{
// Synchronisation durch Prioritaet ?
jTextFieldA[2].setText(sleepthread1) ;
// sleep-Zeiten
jTextFieldA[6].setText(sleepthread2) ;
// zuruecksetzen.
}
195
8 Anhang
sync = true;
// Synchr. durch Ereignisse auf true
syncjoin=false; // Synchr. auf das Ende von Thread 2 auf false
syncprio.setsync(false); // Synchr.durch Prioritaet auf false
setsync();
// Anzahl der Ausgaben ermitteln
textArea1.put("Ausgabe wird auf "+jTextFieldA[11].getValue()+"
synchronisiert\n");
}
// Methode wir ausgefuehrt, wenn
// durch die Prioritaet synchronisiert werden soll
void groupBox2_itemStateChanged(ItemEvent e) {
if(thindex[0]==1)
// Wenn Thread 1 laeuft
thread1.meinstop();
// wird dieser beendet.
stop2.set(true);
// Objekt fuer Thread 2
// wird auf beenden gesetzt
if(jTextFieldA[12].isValid()==false) // Textfeld fuer die
{
// Schleifenzahl
jTextFieldA[12].requestFocus();
// gueltig ?
}
else
// Schleifenzahl wird ausgelesen und uebergeben.
syncprio.setschleifen(jTextFieldA[12].getValue());
sync=false;
// Synchr. durch Ereignisse auf false
syncjoin=false; // Synchr. auf das Ende von Thread 2 auf false
syncprio.setsync(true); // Synchr.durch Prioritaet auf true
sleepthread1 = jTextFieldA[2].getText();
sleepthread2 = jTextFieldA[6].getText();
jTextFieldA[2].setText("Schleifenzahl");
jTextFieldA[6].setText("Schleifenzahl");
// Werte fuer die sleep-Zeit der Threads wird
// gesichert und der Text "Schleifenzahl"
// in die entsprechenden Felder eingesetzt.
setsync();
textArea1.put("Synchronisation mit Priorität\n");
if (jTextFieldA[12].isValid())
{
// Schleifenzahl wird ermittelt und ausgegeben
syncprio.setschleifen(jTextFieldA[12].getValue());
textArea1.put("Anzahl der Schleifen:
"+jTextFieldA[12].getValue()*500000+"\n");
}
}
// Methode wird ausgefuehrt, wenn
// auf das Ende des zweiten Threads synchronisiert.
void groupBox3_itemStateChanged(ItemEvent e) {
if(thindex[0]==1)
// Wenn Thread 1 laeuft
thread1.meinstop();
// wird dieser beendet.
stop2.set(true);
// Objekt fuer Thread 2 wird
// auf beenden gesetzt
196
8 Anhang
if (syncprio.getsync())
{
// Sleep-Zeiten neu eingetragen
jTextFieldA[2].setText(sleepthread1) ;
jTextFieldA[6].setText(sleepthread2) ;
}
sync=false;
// Synchr. durch Ereignisse auf false
syncprio.setsync(false); // Synchr. durch Prioritaet auf false
syncjoin=true;
// Synchr. auf das Ende von Thread 2 auf true
setsync();
textArea1.put("Synchronisation auf Ende von Thread 2\n");
}
// Methode wird ausgefuehrt, wenn
// nicht synchronisiert werden soll.
void groupBox4_itemStateChanged(ItemEvent e) {
if(thindex[0]==1)
// Wenn Thread 1 laeuft
thread1.meinstop();
// wird dieser beendet.
stop2.set(true);
// Objekt fuer Thread 2 wird
if (syncprio.getsync())
// auf beenden gesetzt.
{
jTextFieldA[2].setText( sleepthread1) ;
jTextFieldA[6].setText( sleepthread2) ;
}
sync=false;
// Synchr. durch Ereignisse auf false
syncprio.setsync(false); // Synchr. durch Prioritaet auf false
syncjoin=false; // Synchr. auf das Ende von Thread 2 auf false
setsync();
textArea1.put("Keine Synchronisation\n");
}
// Methode wird ausgefuehrt, wenn eine Eingabe
// im Textfeld 11 erfolgte. Dieses Eingabefeld
// bestimmt die Anzahl der Ausgaben.
void jTextFieldA11_actionPerformed(ActionEvent e) {
if (sync && jTextFieldA[11].isValid())
// Eingabe des Textfeldes wird ueberprueft.
textArea1.put("Ausgabe wird auf "+jTextFieldA[11].getValue()+"
synchronisiert\n");
setsync();
// Methode wird aufgerufen um ggf. Werte
}
// zur Synchronisation zu uebergeben.
// Methode wird ausgefuehrt, wenn eine Eingabe
// im Text Feld 11 erfolgte.Dieses Eingabefeld
// bestimmt die Anzahl der Schleifen.
void jTextFieldA12_actionPerformed(ActionEvent e) {
if (syncprio.getsync() && jTextFieldA[12].isValid())
{
syncprio.setschleifen(jTextFieldA[12].getValue());
textArea1.put("Anzahl der Schleifen:
"+jTextFieldA[12].getValue()*500000+"\n");
}
}
}
197
8 Anhang
// Klasse des action_Adapter fuer das Eingabefeld 11
class Applet1_jTextFieldA11_actionAdapter implements
java.awt.event.ActionListener {
Applet1 adaptee;
Applet1_jTextFieldA11_actionAdapter(Applet1 adaptee) {
this.adaptee = adaptee;
}
public void actionPerformed(ActionEvent e) {
adaptee.jTextFieldA11_actionPerformed(e);
}
}
// Klasse des action_Adapter fuer das Eingabefeld 12
class Applet1_jTextFieldA12_actionAdapter implements
java.awt.event.ActionListener {
Applet1 adaptee;
Applet1_jTextFieldA12_actionAdapter(Applet1 adaptee) {
this.adaptee = adaptee;
}
public void actionPerformed(ActionEvent e) {
adaptee.jTextFieldA12_actionPerformed(e);
}
}
8.4.2 Die Klasse Beenden.java
package BeispielApplet;
// Klasse um den Threads, die mit Hilfe des
// Runnable-Objektes erzeugt wurden
// mitzuteilen, ob sie sich beenden sollen.
public class Beenden {
private boolean stop;
// Konstruktor: Beim Erzeugen des Objekts muß
// der Anfangswert übergeben werden.
public Beenden(boolean wert) {
this.stop=wert ;
}
// Methode um den Wert des Objektes zu aendern.
public void set(boolean wert){
this.stop=wert;
}
// Methode um den Wert des Objektes abzufragen
public boolean get(){
return this.stop;
}
}
198
8 Anhang
8.4.3 Die Klasse Counter.java
package BeispielApplet;
// Klasse die einen Zaehler enthaelt
public class Counter {
private int start;
private int finish;
private long time;
MeineTextArea textArea1;
//
//
//
//
Startwert des Zaehlers
Ende des Zaehlers
Zeit, die der Zaehler zwischen
den Zaehlerschritten "schlafen" soll
// TextArea fuer die Ausgabe
// Konstruktor: Beim Erzeugen wird die Text Area uebergeben,
// auf die die Ausgabe erfolgen soll.
public Counter(MeineTextArea textArea1) {
this.textArea1 = textArea1;
}
// Daten fuer den Zaehler werden uebernommen
// und der Zaehler wird ausgefuehrt.
public void setcounter (int from, int to, long zeit){
this.start = from;
this.finish = to;
this.time = zeit;
textArea1.put("Counter getstartet\n");
for (int i=start;i<=finish; i++)
// Schleife fuer den Zaehler
{
try{Thread.sleep(time);}
// Sleep-Aufruf
catch (InterruptedException e) {}
textArea1.put("Conter: "+i+"\n"); // Ausgabe des Zaehlerstandes
}
textArea1.put("Counter beendet\n");
}
}
199
8 Anhang
8.4.4 Die Klasse CountingThread.java
package BeispielApplet;
import javax.swing.*;
// Klasse eines Zaehlers, die von der
// Klasse Thread abgeleitet wird.
public class CountingThread extends Thread {
private int start;
private int finish;
// Variablen wie bei der Klasse Counter
private long time;
private boolean stop = false;
// Variable fuer eigene
// "stop"-Methode des Threads
MeineTextArea textArea1;
Steuerung steuer;
Syncprio syncprio;
// Objekte, die spaeter uebergeben werden
JLabel jLabel12;
// Konstruktor: Beim Erzeugen werden Objekte
// von anderen Klassen uebergeben.
public CountingThread(Syncprio syncprio,MeineTextArea
textArea1,Steuerung steuer,JLabel jLabel12){
this.textArea1 = textArea1;
this.steuer = steuer;
this.syncprio=syncprio;
this.jLabel12=jLabel12;
}
// Modifizierte "stop"-Methode
public void meinstop(){
stop = true;
}
// Methode um die Daten fuer den Zaehler zu uebernehmen.
public void setcountingthread(int from, int to,long zeit){
this.start = from;
this.finish = to;
this.time = zeit;
}
// Modifizierte "start"-Methode. Sie dient zum Starten,
// wenn auf das Ende eines andern Thread gewartet werden soll
public void joinset(Thread thread){
try{thread.join();}
catch (InterruptedException f) {}
super.start();
}
// run-Methode des Threads
public void run(){
textArea1.put("Thread 1 gestartet \n");
for(int i=start; i<= finish; i++) // Zaehlerschleife des Threads
{
if (!stop)
// Es wird geprueft ob der
{
// Thread beendet werden soll.
this.yield();
steuer.anhalten();
// Es wird geprueft ob der
// Thread anhalten soll.
200
8 Anhang
// Wenn die Threads durch Prioritaet synchronisiert
// werden sollen, wird die Anzahl der Methodenaufrufe
if (syncprio.getsync())
// ermittelt
{
for (int k=0;k<syncprio.getschleifen();k++)
syncprio.zeit();
}
else
// Erfolgt keine Synchronisation durch Prioritaet
{
// wird die sleep-Methode aufgerufen.
try{Thread.sleep(time);}
catch (InterruptedException e) {}
}
textArea1.put1("Thread 1: "+i+"\n");
}
// Ausgabe des Zaehlerstandes
}
if(stop)
textArea1.put("Thread 1 gestoppt\n"); // Ausgabe wenn der
else
// Thread gestoppt wird.
textArea1.put("Thread 1 beendet\n");
jLabel12.setText("");
textArea1.set(false,1);
// Die letzten beiden Methoden werden
textArea1.put1("");
// ausgefuehrt um eine eventuelle
}
// Synchronisation durch
}
// Ereignisse zu beenden.
8.4.5 Die Klasse CountingThreadRunnable.java
package BeispielApplet;
import javax.swing.*;
// Klasse eines Zaehlers, mit dem Interface Runnable
public class CountingThreadRunnable implements Runnable {
private int start;
private int finish;
private long time;
// Variablen wie bei der Klasse Counter
MeineTextArea textArea1;
Steuerung steuer;
Beenden stop;
// Objekte die spaeter uebergeben werden
Syncprio syncprio;
JLabel jLabel13;
// Konstruktor: Beim Erzeugen werden Objekte von anderen
// Klassen und die Daten fuer den Zaehler uebernommen
public CountingThreadRunnable(JLabel jLabel13,Syncprio syncprio,
Beenden stop2,Steuerung steuer,
MeineTextArea textArea1,int from, int to,long zeit) {
this.textArea1=textArea1;
this.start = from;
this.finish = to;
this.time = zeit;
this.steuer = steuer ;
this.stop = stop2;
this.syncprio=syncprio;
this.jLabel13=jLabel13;
}
201
8 Anhang
// run-Methode des Interfaces Runnable
public void run(){
textArea1.put("Thread 2 gestartet\n");
for (int i=start;i<=finish; i++)
// Zaehlerschleife
{
steuer.anhalten();
// Soll angehalten werden ?
if(stop.get())
// Soll gestoppt werden ?
break;
// Wenn die Threads durch Prioritaet synchronisiert
// werden sollen wird die Anzahl der Methoden-Aufrufe
if(syncprio.getsync())
// ermittelt.
{
for (int k=0;k<syncprio.getschleifen();k++)
syncprio.zeit();
}
// Erfolgt keine Synchronisation durch Prioritaet
else
// wird die sleep-Methode aufgerufen.
try{Thread.sleep(time);}
catch (InterruptedException e) {}
textArea1.put2("Thread 2: "+i+"\n");
}
// Ausgabe des Zaehlerstandes
if (stop.get())
// Ausgabe, wenn der
textArea1.put("Thread 2 gestoppt\n"); // Thread gestoppt wird.
else
textArea1.put("Thread 2 beendet\n");
jLabel13.setText("");
textArea1.set(false,1);
// Die letzten beiden Methoden
textArea1.put2("");
// werden ausgefuehrt um eine
}
// eventuelle Synchronisation durch
}
// Ereignisse zu beenden.
202
8 Anhang
8.4.6 Die Klasse ImageCanvas.java
package BeispielApplet;
import
import
import
import
java.awt.Canvas;
java.applet.Applet;
java.awt.*;
java.awt.Image;
// Klasse ImageCanvas mit dem Interface Runnable
public class ImageCanvas extends Canvas implements Runnable{
private Image images[];
private int currentimage = 0;
Steuerung steuer;
Beenden stop;
Syncprio syncprio;
// Feld fuer die Bilder,
// die ausgegeben werden sollen.
// Variable fuer Bild
// Objekte die spaeter uebergeben werden
// Konstruktor: Hier wird ein Feld, das die Bilder enthaelt
// und Objekte anderer Klassen uebergeben.
public ImageCanvas(Image images[],Steuerung steuer,
Beenden stop3,Syncprio syncprio) {
this.images = images;
this.steuer = steuer;
this.stop
= stop3;
this.syncprio = syncprio;
}
// Methode zum Zeichnen des Bildes
public void paint(Graphics g){
update(g);
}
// Methode zum Zeichnen des Bildes
public void update(Graphics g){
g.drawImage(images[currentimage],0,0,120,100,this);
}
// run-Methode des Interfaces Runnable
public void run(){
while(true)
{
if (stop.get())
// Soll gestoppt werden ?
break;
steuer.anhalten();
// Soll angehalten werden ?
repaint();
// Zeichnung des Bildes
for (int k=1;k<5000;k++)
// Schleife mit der das System
syncprio.zeit();
// belastet und die
// Bildwiederholrate gesteuert wird.
}
currentimage = (currentimage + 1) % images.length;
// Variable fuer Bild wird geaendert
}
}
203
8 Anhang
8.4.7 Die Klasse IntJTextField.java
package BeispielApplet;
import javax.swing.JTextField;
// Klasse wurde von JTextField abgeleitet und einige
// Methoden ergaenzt bzw. der Konstruktor veraendert.
public class IntJTextField extends JTextField {
private int low;
private int high;
// untere Grenze des Wertebereiches
// obere Grenze des Wertebereiches
// Konstruktor der Klasse dem beim Erzeugen des Objektes
// wird der Wertebereich der Eingabe uebergeben.
public IntJTextField ( int min, int max){
this.low = min;
this.high = max;
}
// Methode um festzustellen, ob die Eingabe des Objektes
// ein Int-Wert im erlaubten Wertebereich ist.
public boolean isValid(){
int value;
try
{
// Eingabe wird in einen Int-Wert umgewandelt.
value = Integer.parseInt(this.getText()) ;
if (value < low || value > high)
{
// Liegt der eingegebene
throw new NumberFormatException(); // Wert außerhalb des
}
// Wertebereiches
}
// erfolgt Fehlermeldung
catch (NumberFormatException f)
{
// Es wird kontrolliert ob der Fehler durch
// die fruehere Ausgabe "k.p.D." entstanden ist.
if (this.getText().equals("keine pos. Differenz"))
return false;
this.setText(" "+this.low+ "-" +this.high+" eingeben");
this.selectAll();
// Bei einem Fehler
this.requestFocus();
// wird der Wertebereich
return false;
// angezeigt und das betreffende
}
// Textfeld markiert.
return true;
}
// Methode zum Auslesen des Textfeldes
public int getValue(){
return Integer.parseInt(this.getText());
}
}
204
8 Anhang
8.4.8 Die Klasse MeineTextArea.java
package BeispielApplet;
import java.awt.TextArea;
import java.awt.Color;
// Klasse wurde von TextArea abgeleitet
// und einige Methoden ergaenzt
public class MeineTextArea extends TextArea {
private
private
private
boolean
String buffer;
int soll;
int anzahl=0;
th1 = true;
boolean sync = false;
//
//
//
//
//
//
Anzahl der soll Ausgaben
Anzahl der ist Ausgaben
Variable die bei einer Synchr.
angibt welcher Thread den Monitor erhaelt
Variable die angibt ob eine Synchr.
durch Ereignisse stattfinden soll.
// Konstruktor
public MeineTextArea() {
}
// Methode um die Synchronisation durch
// Ereignisse zu starten und beenden
public void set(boolean syncronisieren,int anzahl){
this.sync=syncronisieren;
// Soll synchronisiert werden wird
this.soll=anzahl;
// die soll Anzahl uebergeben
}
// Synchronisierte Methode fuer die Ausgabe von Thread 1
public synchronized void put1 (String ausgabe){
if (sync)
// Stellt fest, ob synchronisiert werden soll
{
if (!th1)
// Wenn th1 = false
try {wait();}
// muß Thread 1 warten.
catch(InterruptedException e) {}
this.buffer=ausgabe;
this.append(buffer); // Ausgabe des uebergebenen Strings
anzahl=anzahl+1;
if (anzahl>=soll)
{
anzahl=0;
th1=false;
// Variable fuer den Monitor wird umgesetzt
notify();
// Eventuell wartender Thread erhaelt die
}
// Benachrichtigung, daß er weiter arbeiten darf.
}
else
// Soll keine synchronisierte
{
// Ausgabe mehr stattfinden weil z.B.
this.buffer=ausgabe;
// nur noch ein Thread laeuft
this.append(buffer);
// wird diese Schleife abgearbeitet.
notify();
}
}
205
8 Anhang
// Synchronisierte Methode fuer die Ausgabe von Thread 2
public synchronized void put2 (String ausgabe){
if (sync)
// Stellt fest, ob synchronisiert werden soll.
{
if (th1)
// Wenn th1 = true
try {wait();}
// muß Thread 2 warten.
catch(InterruptedException e) {}
this.buffer=ausgabe;
this.append("
"+buffer); // Ausgabe des Strings
anzahl=anzahl+1;
// Die Anzahl wird heraufgesetzt.
if (anzahl>=soll)
// Vergleich von soll und ist Wert
{
anzahl=0;
th1=true;
// Variable fuer den Monitor wird umgesetzt
notify();
// Eventuell wartender Thread erhaelt die
}
// Benachrichtigung, daß er weiter arbeiten darf.
}
else
// Soll keine synchronisierte
{
// Ausgabe mehr stattfinden weil z.B.
this.buffer=ausgabe;
// nur noch ein Thread laeuft wird
if (!buffer.equals(""))
// diese Schleife abgearbeitet.
this.append("
"+buffer);
notify();
}
}
// Synchronisierte Methode fuer die Ausgabe
// von allgemeinen Informationen.
public synchronized void put (String ausgabe){
this.buffer=ausgabe;
this.append("
"+buffer);
}
}
206
8 Anhang
8.4.9 Die Klasse Steuerung.java
package BeispielApplet;
// Diese Klasse wird verwendet um die Threads mit Hilfe
// eines Objektes dieser Klasse
// anzuhalten und wenn gewollt wieder weiter zu fuehren.
public class Steuerung {
private boolean halt;
// Konstruktor dem beim Erzeugen eines Objektes
// der Anfangswert uebergeben werden muß
public Steuerung(boolean wert) {
this.halt=wert;
}
// Methode um der Wert des Objektes auf true zu setzen
public void setanhalten (){
this.halt=true;
}
// Methode bei dessen Ausfuehrung ein Thread
// angehalten wird, wenn das "Steuerugsobjekt" true ist.
public synchronized void anhalten (){
if (this.halt)
{
try {wait();}
catch(InterruptedException e) {}
}
}
// Methode um den Wert des Objektes auf false zu setzen.
public synchronized void weiter(){
this.halt=false;
notifyAll();
// Eventuell wartende Threads werden
}
// benachrichtigt, daß sie weiter
}
// ausgefuehrt werden koennen.
207
8 Anhang
8.4.10 Die Klasse Syncprio.java
package BeispielApplet;
// Klasse die fuer eine Synchronisation
// durch Prioritaet benoetigt wird.
public class Syncprio {
private boolean sync;
private int schleifen;
// Konstruktor Die Schleifenzahl und ob
// eine Synchronisation durch die Prioritaet erfolgen
// soll wird beim Erzeugen festgelegt.
public Syncprio(boolean wert, int schleifen) {
this.schleifen=schleifen;
this.sync=wert;
}
// Methode mit der die Schleifenzahl veraendert werden kann
public void setschleifen(int schleifen){
this.schleifen=schleifen*500;
}
// Methode ueber die festgelegt wird ob eine Synchronisation
// durch die Prioritaet erfolgen soll oder nicht
public void setsync(boolean wert){
this.sync=wert;
}
// Methode ueber die die Schleifenzahl erfragt werden kann
public int getschleifen(){
return this.schleifen;
}
// Methode ueber die erfragt werden kann ob eine
// Synchronisation durch die Prioritaet erfolgen soll
public boolean getsync(){
return this.sync;
}
// Methode um bei einer Synchronisation
// durch die Prioritaet das System zu belasten
public synchronized int zeit(){
int k=0;
for (int i=0;i<=1000;i++)
k=k+1;
return k;
}
}
208
8 Anhang
8.4.11 Implementierung des Beispiel Applets in HTML-Seite
Durch diese Implementierung beginnt ein Browser, der nicht Java fähig ist auf Wunsch
des Anwenders selbstständig mit dem Download des benötigten Plug-In.
Source-Code der HTML-Datei:
<HTML><HEAD>
<META HTTP-EQUIV="Content-Type"
CONTENT="text/html; charset=windows-1252">
<TITLE>
HTML-Testseite
</TITLE></HEAD><BODY>
Das BeispielApplet erscheint in einem Java-fähigen Browser.<BR>
<!--"CONVERTED_APPLET"-->
<!-- CONVERTER VERSION 1.3 -->
<OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
WIDTH = 635 HEIGHT = 500 NAME = "TestApplet"
ALIGN = middle VSPACE = 0 HSPACE = 0
codebase="http://java.sun.com/products/plugin/1.3/jinstall-13win32.cab#Version=1,3,0,0">
<PARAM NAME = CODE VALUE = "BeispielApplet.Applet1.class" >
<PARAM NAME = CODEBASE VALUE = "." >
<PARAM NAME = NAME VALUE = "TestApplet" >
<PARAM NAME="type" VALUE="application/x-java-applet;version=1.3">
<PARAM NAME="scriptable" VALUE="false">
<COMMENT>
<EMBED type="application/x-java-applet;version=1.3"
CODE = "BeispielApplet.Applet1.class"
CODEBASE = "." NAME = "TestApplet" WIDTH = 635 HEIGHT = 500
ALIGN = middle VSPACE = 0 HSPACE = 0
scriptable=false
pluginspage="http://java.sun.com/products/plugin/1.3/plugininstall.html"><NOEMBED></COMMENT>
</NOEMBED></EMBED></OBJECT>
<!-<APPLET CODE = "BeispielApplet.Applet1.class" CODEBASE = "." WIDTH = 635
HEIGHT = 500 NAME = "TestApplet" ALIGN = middle VSPACE = 0 HSPACE = 0>
</APPLET>
-->
<!--"END_CONVERTED_APPLET"-->
</BODY></HTML>
209