Professional Documents
Culture Documents
Facharbeit
im Fach
Informatik
Klasse 12.1
Werner-von-Siemens-Gymnasium Magdeburg
Marcel Bassüner
4 Parallele Programmiersprachen 11
6 Fazit 18
7 Anhang 19
8 Literaturverzeichnis 22
1
1 Einleitung
Das Thema dieser Facharbeit lautet Parallele Programmierung. Der Umfang der Pro-
blematik war Anlass dafür, die spezialisierte Ausarbeitung auf ”Heimcomputer”(PC)
und deren Software, zu beschränken
1
Mey, an Dieter: ”Parallel Programming in MPI. Parallelization Strategies”. Center for Computing
and Communication Aachen University of Technology, 28.01.2003
2
Bräunl, Thomas: ”Parallel Programmierung. Eine Einführung”. Paderborn: Vieweg, 1993 S.2
3
Spieß, Gunnar: ”Proseminar: Paralleles und verteiltes Rechnen. Thema: Parallele Programmierung
in Java”. Wilhelm-Schickard-Institut für Informatik - Abteilung Technische Informatik, 28.07.2003
2
Der Leser soll einen kleinen Einblick in parallele Architekturen bekommen und an
die parallele Programmierung heran geführt werden. Dazu werden folgende Fragen
gestellt:
3
2 Parallelität: Varianten und Ebenen
2.1 Varianten der Parallelität
• Sequentielle Arbeitsweise
• Parallele Arbeitsweise
• Nebenläufigkeit
• Pipelining
Nebenläufigkeit liegt vor wenn mehrere Instruktionen zu einem Zeitpunkt auf asyn-
chron oder synchron arbeiten Einheiten ausgeführt werden.
Von Pipelining ist die Rede 4 : ”. . . wenn die Ausführung der Instruktionen der abstrak-
ten Maschine in synchron getaktete, sequentiell zu durchlaufende Teilausführungsschritte
zerlegt ist (die Phasen der Pipeline), die gegeneinander überlappt bearbeitet werden.” 5
Abbildung 1 stellt dies noch einmal grafisch dar.
Nebenläufigkeit und Pipelining ergänzen sich gegenseitig als Prinzipien und können
auch gemeinsam in abstrakten Maschinen vorkommen. 6
4
Waldschmidt, Klaus: ”Parallelrechner. Architekturen - Systeme - Werkzeuge.”.Leitfäden der Infor-
matik. Stuttgart, B.G. Teubner Stuttgart, 1995, S.13f
5
Waldschmidt: a.a.O. S.14
6
Ebd.
4
Abbildung 1: Pipelining
URL: http://de.wikipedia.org/wiki/Bild:Befehlspipeline.PNG“ (Stand 15.02.07)
”
5
2.2 Ebenen der Parallelität
Die Parallelität von Ereignissen kann auf verschieden Ebenen umgesetzt werden. So
unterscheidet man zwischen vier Stufen, wobei die eigentliche Parallelität, von Stufe zu
Stufe, immer feinkörniger wird. Jede Ebene vereint grundverschiedene Aspekte einer
parallelen Arbeitsweise. Konstrukte und Verfahrensweisen einer Ebene beschränken
sich auf diese und lassen sich meist nicht auf eine andere Ebene übertragen. Bei den
vier Ebenen handelt es sich um:
• Programmebene
• Prozedurebene
• Ausdrucksebene
• Bitebene
Programmebene
Dies ist die oberste Ebene, auf ihr laufen komplette Programme parallel (zumindest
scheinbar)ab. Der ausführende Computer muss in diesem Fall kein Parallelrechner sein,
es genügt ein Multitasking-Betriebssystem (z.B. durch das Zeitscheiben-Prinzip7 ).Die
Verarbeitungseinheiten der Ebene heißen Jobs und Tasks (daher hat der Windows
Taskmanager seinen Namen).
Prozedurebene
Auf dieser Ebene wird ein Programm in einzelne Programmfäden zerlegt, welche dann
nebenläufig arbeiten. Diese Programmteile nennt man Prozesse“ oder Theads8“ und
” ”
sind einzeln betrachtet sequentielle Prozeduren. Um dies Umzusetzen müssen Probleme
in unabhängige Teilprobleme zergliedert werden, damit es nur selten zu einem aufwen-
digen Datentransfer zwischen den Programmfäden, kommt. Anwendungsgebiete sind
nicht nur die allgemeine Parallelverarbeitung von Programmen sondern auch die Re-
gelung von Prozessrechnern zur Gleichzeitigen Ansprache von Hardwarekomponenten
bei der Robotersteuerung.
7
siehe Abschnitt 5
8
siehe Abschnitt 5
6
Ausdrucksebene
Bitebene
Die Letzte und somit fein körnigste Ebene ist die Bitebene. Die einzelnen Bit-Operation
werden in einem Wort vereint und dadurch parallelisiert.
Listing 1: Parallelität auf Bitebene
1 01100101
2 AND 10101101
3 00100101
9
Diese Schicht der Parallelität findet man in fast jedem Mikroprozessor.
9
Bräunl: a.a.O. S.11ff
7
3 Betriebssysteme für Parallelrechner
Die Eigenschaften eines Parallelrechners beeinflusst die Wahl eines geeigneten Betriebs-
systems sehr. Das Betriebssystem muss zum einen selbst die Parallelität nutzen und
zum anderen hat es die Aufgabe Anwendungen die Nutzung der Parallelität auf ein-
fachste weise möglich zu machen. Auch wenn in Einzelfällen Betriebssysteme speziell für
Parallelrechner entwickelt wurden, reich in der Regel die Zeitspanne zwischen erstem
Hardwareprototyp und benötigtem Betriebssystem nicht aus. Somit sind die Hersteller
Gezwungen auf bereits vorhandene System zurückzugreifen.
Im Folgenden werden drei Betriebssystemarten vorgestellt und auf ihre Tauglichkeit
für Parallelrechner untersucht, da parallele Architekturen meist für sehr rechenintensi-
ve Aufgaben benutzt werden. 10
• Prozessverwaltung
• Speicherverwaltung
• Dateisystem
• Netzwerkkommunikation
Diese bereitgestellten Mechanismen können meist nicht ausgewechselt werden, d.h sie
sind für alle Benutzer zwingend festgelegt. Größtenteils kann man auf nicht benötigte
Teile auch nicht verzichten dadurch verbrauchen diese unnötigerweise Speicher oder
sogar Rechenleistung. Ein weiterer Nachteil ist der große Zeitaufwand für Anwender-
programme um Systemfunktionen zu nutzen, da durch so genannte supervisor calls“
”
der richte Einstig in den Systemkern gefunden werden muss. Im Gegensatz dazu steht
die hervorragende Kommunikation innerhalb des Systemkerns.
In den 80er Jahren wurde versucht den Unix-Kern zu verkleinern. Dazu wurden Un-
tersuchungen durchgeführt welche Teile aus dem bisher monolithischen Kern entfernt
werden können. Aus dieser Arbeit resultierten die Minimalkerne mit ausgelagerten Be-
triebssystemfunktionen.
10
Waldschmidt: a.a.O. S.341ff
8
3.2 Minimalkerne (Low-Level-Kernel) mit
ausgelagerten Betriebssystemfunktionen
• Die Modularisierung 13 ist aus Effizienzgründen besser, jedoch leider nur sehr
grob vorhanden. Dadurch können diese Module schnell zu Engpässen werden, da
innerhalb, keine Modulinterne Nebenläufigkeit realisierbar ist.
Das Ziel war es den eingeschlagenen Weg weiter zu verfolgen, das heißt feinere Modula-
risierung. Jedoch muss das aufgetretene Kommunikationsproblem zwischen den Modu-
len behoben werden. Der Ansatz ist die Module die häufig miteinander kommunizieren
in den selben Adressraum zu schreiben. Dadurch verliert man die Modulkapselung,
welches eines der ersten Ziele war. Somit muss diese auf einem anderen Weg erreicht
werden.
Eine Möglichkeit ist die Überprüfung der Module bereits bei der Übersetzung ob die
Operationen modulübergreifend sind oder sich nur auf Daten innerhalb des Moduls
beziehen. Dies ist kein vollständiger Ersatz für die Kapselung jedoch reich es für An-
wendungen aus. Betriebssysteme sind wesentlich komplexer somit ist diese Variante
nicht hinreichend.
Einen Ausweg bietet die objektorientierte Programmierung, welche Hilfsmittel für die
11
Waldschmidt: a.a.O. S.356ff
12
Waldschmidt: a.a.O. S.357f
13
Module: Die einzelnen ausgegliederten Teile des Betriebssystems.
9
Bildung von Kommunikationsschnittstellen und die Kapselung bietet. Auf verschie-
dene Adressräume kann verzichtet werden da die einzelnen Objekte klar voneinander
abgegrenzt sind. Dadurch können wie im monolithischen Systemkernen wieder alle Be-
triebssystemobjekte in einen Adressraum gelegt werden, da nach wie von die Kapselung
bestehen bleibt.
Durch diesen Aufbau vermeidet man die Strukturierungsprobleme monolithischer Sys-
temkerne und kann trotzdem, durch die eindeutigen Schnittstellen der Objekte, An-
passungen an das System durchführen.
Durch die feine Strukturierung der Objekte kann auf objektinterne Nebenläufigkeit ver-
zichtet werden. Demzufolge müssen nur die Kommunikationsschnittstellen koordiniert
werden.14
14
Waldschmidt: a.a.O. S.358ff
10
4 Parallele Programmiersprachen
Programmiersprachen geben uns die Möglichkeit parallele Architekturen zu nutzen.
Durch sie können Programme geschaffen werden welchen die Parallelität nutzen.
Als erstes unterscheidet man vier Sprachparadigmen:
Für alle diese Gruppen existieren Möglichkeiten um sie für parallele Programmierung
zu nutzen. Es gibt einige Ansätze bei denen Versucht wurde bestehende Sprachen
abzuwandeln und zu erweitern, um sie für parallele Anwendungen zu nutzen, wie zum
Beispiel für C++ und Fortran (OpenMP, Cilk). Jedoch setzten sich im Allgemeinen
Bibliotheken (PVM, MPI, Pthreads,Win32 Threads) als Erweiterungen durch. Sehr
seltene Fälle sind die extra entwickelten Sprachen (symmetrische Sprache, Erlang) für
parallele Programmierung.
Egal welche Sprache gewählt wird, es folgt eine weitere Unterscheidung. Es wird nach
zwei Ansätzen für die Verarbeitung der Parallelität differenziert:
Implizite Parallelität ist die einfachere Methode für den Programmierer, da keine
Änderungen an einem bestehenden sequentiellen Quellcode vorgenommen wer-
den müssen. Bei diese Form der Parallelverarbeitung gibt es zwei Möglichkeiten:
1. Man kann einzelne sequentielle Funktionen durch den Aufruf von Methoden,
aus parallelen Bibliotheken ersetzen. Meistens sind die Bibliotheken sehr
begrenzt und nur für mathematische Algorithmen (Intel MKL) zu finden.
Explizite Parallelität setzt oftmals eine umfassende Änderung des sequentiellen Quell-
textes voraus. Auch hier gibt es verschiedene Varianten der Umsetzung. Bei einem
inkrementellen Ansatz handelt es sich um rechenintensive Bereiche die paralleli-
siert werden (OpenMP). Eine andere Methode ist die Nebenläufigkeit von rechen-
15
Dr. Deilmann, Mario: Parallelwelten“,in entwickler magazin“. S&S: Ausgabe Januar/Februar 2007.
” ”
S.26ff
11
intensiven Bereichen, mit der Steuerung eines Kontrollflusses (Threads, MPI).
Wiederum vertreten Threads und MPI verschiedene Klassen. Bei den Threads
werden Anwendungen in einzelne, voneinander weitest gehend unabhängige Teil-
probleme zerlegt. Diese Programmfäden“ werden nebenläufig organisiert. Hier-
”
bei wird über einen gemeinsamen Speicher kommuniziert.
MPI hingegen regelt die Kommunikation der Teilprobleme über Nachrichten.
Somit erklärt sich auch deren Verwendung in Netzwerken. Von einem Steuer-
rechner wird das Programm gestartet. Die einzelnen Teilprobleme laufen nun
jeweils auf einem Rechner im Netzwerk wodurch die Parallelität erreicht wird.
Die Ergebnisse der Berechnungen werden anschließend per Nachrichten über das
Netzwerk versendet.16 Häufig stellt dadurch die Kommunikation zwischen den
Programmfäden“ einen Engpass dar, weshalb die Nachrichten Übertragung so
”
gering wie möglich gehalten werden sollte.
16
Ebd.
12
5 Entwicklung paralleler Programme
Der letzte Punkt dieser Arbeit befasst sich mit der eigenen Verwendung von Parallelität
in Anwendungen. Parallelität kann in verschieden Situationen sehr hilfreich und manch-
mal auch nötig sein. Wie schon in der Einleitung angesprochen laufen in Simulationen
viele Dinge parallel ab. Somit wäre sie ein Beispiel für die Performancesteigerung durch
Parallelität. Zum anderen gibt es auch die Möglichkeit zur Erhöhung der Sicherheit.
Dies kommt zum Beispiel bei der Spiegelung von Festplatten zum Einsatz, Änderungen
werden parallel an der original und an der gespiegelten Festplatte vorgenommen.
Die erste Variante ware Flags“. Das sind Status-Flaggen die den Zustand der Be-
”
triebsmittel anzeigen. Wenn der Drucker nun von einem Prozess verwendet wird die
17
Burkhard,Steffen: Parallele Rechnersysteme. Programmierung und Anwendung“. Berlin, München:
”
Verl. Technik, 1993. S.98
18
Bräunl:a.a.O. S.12
19
Spieß: a.a.O S.6
13
Drucker-Flagge auf besetzt gestellt. Kommen zu diesem Zeitpunkt weitere Druckeran-
fragen werden diese Prozesse in eine Warteschleife gestellt bis der Drucker wieder frei-
gegeben ist. Das Freigeben geschieht wenn die Drucker Nutzung eines Prozesses abge-
schlossen ist. In dem Augenblick in dem das passiert bekommen alle Prozesse die sich in
der Warteschlange befanden die Möglichkeit dem Drucker befehle zu erteilen. Dadurch
kam es in einigen Situationen wieder zu Überschneidungen.Es musste also eine andere
Lösung für das Problem gefunden werden.
Diese neue Lösung nannte sich Semaphore. Das Prinzip der Semaphoren wurde 1965
erstmals von E. W. Dijkstra beschrieben. Das Wort selbst kam aus dem Griechischen
und bedeutet Signal. Die Grundstruktur dieser Idee ist den Flags sehr ähnlich. Eine
Semaphore besteht aus zwei Variablen und zwei Prozeduren. Die erste Variable ist ein
Zähler der Werte größer gleich Null annehmen kann. Die Zweite ist eine Warteschlange
in die sich Prozesse nach dem FIFO (first in, first out) einordnen. Dadurch vermei-
det man einen doppelten Zugriff auf Betriebsmittel. Bei der Initialisierung nimmt die
Warteschlange den Wert Leer an und der Zähler den Wert Eins. Beim Eintritt ei-
nes Prozesses wird durch den Aufruf der Verarbeitungsprozedure WAIT“ der Zähler
”
dekrementiert. Solange dies geschieht wenn der Zähler größer Null ist kann der Pro-
zess die Ressource nutzen, sobald er gleich Null ist werden einkommende Prozesse in
die Warteschlange aufgenommen. Anders als bei den Flags durchlaufen sie nun keine
Schleife sondern pausieren. Somit wird deren Rechenzeit nicht verschwendet und an-
dere Prozesse könne diese gewonnene Rechenkapazität nutzen. Wenn ein Prozess das
Betriebsmittel freigibt wird die Prozedur Signal“ aufgerufen, welche den Nächsten aus
”
der Warteschlange holt und auf die Ressource zugreifen lässt.
Die Verwendung von Parallelität wird an der Simulation von einem Einkaufsmarkt ver-
deutlicht: In den Markt kommen regelmäßig neue Kunden, die sich eine zufällige Zeit
im Laden aufhalten und dann zur Kasse gehen. Es gibt eine Warteschlange. Wenn eine
der parallel arbeitenden Kassen einen Kunden abgearbeitet hat, kann der nächste aus
der Warteschlange nachrücken. Die Kassen sollen nach belieben geöffnet oder geschlos-
sen werden können. Jedoch wenn keine Kasse geöffnet ist, ist der Markt geschlossen
und es kommen keine weiteren Kunden (Pause).
Um Threads unter Delphi verwenden zu können, müssen extra Klassen von der Ur-
klasse TThread (uses classes) abgeleitet werden. Dies geschieht damit die Prozedur
Execute“ überschrieben wird, in der später enthalten ist was der Thread eigentlich
”
macht.
20
Borland Delphi7
14
Listing 2: Klassen Vererbung
1 type
2 Kasse = class ( TThread )
3 private
4 { Private - Deklarationen }
5 protected
6 procedure Execute ; override ;
7 end ;
8
9 type
10 Customer = class ( TThread )
11 private
12 { Private - Deklarationen }
13 protected
14 procedure Execute ; override ;
15 public
16 end ;
Passend zu den Variablen müssen somit Prozeduren erstellt werden die die Aufgabe
der Veränderung übernehmen.
15
Listing 4: Variablen ändern
1 procedure TForm1 . IncCustomer ;
2 begin
3 inc ( WaitCustomer ) ;
4 LWaitCustomer . Caption := IntToStr ( WaitCustomer ) ;
5 end ;
6
Damit die Threads auch etwas tun können, müssen die Befehle in die überschriebene
Execute Prozedur.
Listing 5: Execute
1 // - - - - - - - - - - - - - - - - - - - - -
2 // TCustomer
3 // - - - - - - - - - - - - - - - - - - - - -
4
5 procedure TCustomer . Execute ;
6 begin
7 while Terminated = false do
8 begin
9 sleep ( random (2000) ) ;
10 Synchronize ( Form1 . IncCustomer ) ;
11 end ;
12 end ;
13
14 // - - - - - - - - - - - - - - - - - - - - -
15 // KASSE
16 // - - - - - - - - - - - - - - - - - - - - -
17
18 procedure Kasse . Execute ;
19 begin
20 while Terminated = false do
21 begin
22 sleep (2000+ random (3000) ) ;
23 Synchronize ( Form1 . DecCustomer ) ;
24 end ;
25 end ;
Da die Threads in dem Beispiel nur die Aufgabe haben die Variablen zu ändern sind die
Prozeduren recht übersichtlich. Um jedoch Veränderungen vorzunehmen muss die Pro-
zedur (z.B. TForm1.IncCustomer) durch den Befehl: Synchronize(); aufgerufen werden.
Dieses Kommando übernimmt die Arbeit der Semaphore oder der Flags und synchro-
nisiert alle zugreifenden Threads automatisch.
16
Letztlich fehlt noch der Start/ Aufruf der Threads. Hier ist dies an den FormCreate
Prozedur demonstriert.
Listing 6: Start
1 procedure TForm1 . FormCreate ( Sender : TObject ) ;
2 begin
3 Customer := TCustomer . Create ( true ) ;
4 LeaveCustomer :=0;
5 end ;
Customer wird durch den Konstruktor initialisiert und gestartet. Die Boolean Variable
in der Klammer bestimmt ob der Thread pausierend gestartet wird. Der Wert true
startet ihn im Pausen-Modus. Ansonsten wird sofort mit der Ausführung der Execute
Prozedur begonnen.
So sieht das fertige Programm aus:
Abbildung 2: Einkaufsmarkt
17
6 Fazit
Paralle Programmierung ist ein sehr umfassendes Thema. Ausarbeitungen darüber
könnten hunderte von Seiten lang sein. Jedoch Setzte sich diese Ausarbeitung zum
Ziel einen Einblick zu gewähren und den Anwendern, also den Programmierern etwas
näher zu bringen.
Parallelität kann man auf sequentiellen Architekturen auf verschiedenste Weise umset-
zen. Zum Beispiel durch nachrichtenorientierte Systeme wie zum Beispiel MPI, welches
häufig in Netzwerken wie in Unis eingesetzt wird. Moderne Compiler wie Intels /Qpar-
allel bieten die Möglichkeit sequentiellen Quelltext einfach zu parallelisieren, jedoch
sind diese im Umfang noch sehr eingeschränkt. Eine weitere Methode ist der Einsatz
von Bibliotheken die es ermöglichen parallel zu arbeiten. Eine der bekanntesten ist die
Thread-Bibliothek mit dem time sharing“ Verfahren. Nach diesem Prinzip arbeiten
”
die meisten etablierte Betriebssysteme.
Die Verwendung von Threads in Delphi ist sehr einfach. Um kleinere Konstrukte zu
erstellen muss man nur eine neue Klasse von TThread erben lassen und die Execute
Prozedur überschreiben.
Listing 7: Vererben
1 type
2 Thread = class ( TThread )
3 private
4 { Private - Deklarationen }
5 protected
6 procedure Execute ; override ;
7 end ;
Die Prozedur Execute enthält den Code der beim ausführen den Threads abgearbeitet
wird. Nach dem folgenden Schema wird ein Thread erzeugt und gestartet.
Listing 8: Erzeugung
1 TestThread := Thread . Create ( false ) ; // true -> Pause
Mit dem Befehl Suspend kann man einen Thread anhalten und mit de Befehl Resume
wieder fortsetzen.
Listing 9: Steuerung
1 TestThread . Suspend ;
2 TestThread . Resume ;
Mit dem Kommando Terminate besteht die Möglichkeit einen Thread zu beenden.
Wenn in dem Thread eine Endlosschleife ablaufen soll, muss als Abbruchbedingung
Terminated stehen.
Listing 10: Beenden
1 TestThread . Terminate ;
2 TestThread . Terminated = True / False
18
7 Anhang
Listing 11: Einkaufsmarkt
1 unit Markt ;
2
3 interface
4
5 uses
6 Windows , Messages , SysUtils , Variants , Classes , Graphics , Controls ,
Forms ,
7 Dialogs , StdCtrls , ExtCtrls ;
8
9
10 type
11 Kasse = class ( TThread )
12 private
13 { Private - Deklarationen }
14 protected
15 procedure Execute ; override ;
16 end ;
17
18 type
19 TCustomer = class ( TThread )
20 private
21 { Private - Deklarationen }
22 protected
23 procedure Execute ; override ;
24 public
25 end ;
26
27
28 type
29 TForm1 = class ( TForm )
30 KasseHinzu : TButton ;
31 Label2 : TLabel ;
32 LWaitCustomer : TLabel ;
33 Label1 : TLabel ;
34 LOpenTill : TLabel ;
35 Bevel1 : TBevel ;
36 KasseWeg : TButton ;
37 Label3 : TLabel ;
38 LCustomer : TLabel ;
39 Label4 : TLabel ;
40 procedure IncCustomer ;
41 procedure DecCustomer ;
42 procedure IncTill ;
43 procedure DecTill ;
44 procedure FormCreate ( Sender : TObject ) ;
45 procedure KasseHinzuClick ( Sender : TObject ) ;
46 procedure KasseWegClick ( Sender : TObject ) ;
47 procedure FormClose ( Sender : TObject ; var Action : TCloseAction ) ;
48 private
49 { Private - Deklarationen }
50 WaitCustomer : integer ;
51 LeaveCustomer : integer ;
19
52 OpenTill : byte ;
53 Customer : TCustomer ;
54 TKassen : array [1..10] of kasse ;
55 public
56 { Public - Deklarationen }
57 end ;
58
59 var
60 Form1 : TForm1 ;
61
62 implementation
63
64 { $R *. dfm }
65
66 // - - - - - - - - - - - - - - - - - - - - -
67 // TCustomer
68 // - - - - - - - - - - - - - - - - - - - - -
69
70 procedure TCustomer . Execute ;
71 begin
72 while Terminated = false do
73 begin
74 sleep ( random (2000) ) ;
75 Synchronize ( Form1 . IncCustomer ) ;
76 end ;
77 end ;
78
79 // - - - - - - - - - - - - - - - - - - - - -
80 // KASSE
81 // - - - - - - - - - - - - - - - - - - - - -
82
83 procedure Kasse . Execute ;
84 begin
85 while Terminated = false do
86 begin
87 sleep (2000+ random (3000) ) ;
88 Synchronize ( Form1 . DecCustomer ) ;
89 end ;
90 end ;
91
92 // - - - - - - - - - - - - - - - - - - - - -
93 // FORM1
94 // - - - - - - - - - - - - - - - - - - - - -
95
96 procedure TForm1 . IncTill ;
97 begin
98 if OpenTill <10 then
99 begin
100 inc ( OpenTill ) ;
101 TKassen [ OpenTill ]:= Kasse . Create ( false ) ;
102 LOpenTill . Caption := IntToStr ( OpenTill ) ;
103 end ;
104 end ;
105
106 procedure TForm1 . DecTill ;
107 begin
20
108 TKassen [ OpenTill ]. Terminate ;
109 dec ( OpenTill ) ;
110 LOpenTill . Caption := IntToStr ( OpenTill ) ;
111 end ;
112
21
8 Literaturverzeichnis
• Bräunl, Thomas: ”Parallel Programmierung. Eine Einführung”. Paderborn: View-
eg, 1993
• Puff, Michael: Thread Programmierung unter Windows mit Delphi“. URL: http://www.luckie-
” ”
online.de“ (Version 2.4)
22
Selbständigkeitserklärung
Ich, Marcel Bassüner versichere hiermit, dass ich diese Facharbeit selbständig verfasst
habe und keine anderen als die angegebenen Quellen benutzt wurden, sowie Zitate
kenntlich gemacht wurden.
23