https://wiki.delphigl.com/index.php?title=Tutorial_Multithreading&feed=atom&action=historyTutorial Multithreading - Versionsgeschichte2024-03-29T14:28:37ZVersionsgeschichte dieser Seite in DGL WikiMediaWiki 1.27.4https://wiki.delphigl.com/index.php?title=Tutorial_Multithreading&diff=23182&oldid=prevFlash: /* Synchronschwimmen */2009-03-22T10:53:44Z<p><span dir="auto"><span class="autocomment">Synchronschwimmen</span></span></p>
<table class="diff diff-contentalign-left" data-mw="interface">
<col class='diff-marker' />
<col class='diff-content' />
<col class='diff-marker' />
<col class='diff-content' />
<tr style='vertical-align: top;' lang='de'>
<td colspan='2' style="background-color: white; color:black; text-align: center;">← Nächstältere Version</td>
<td colspan='2' style="background-color: white; color:black; text-align: center;">Version vom 22. März 2009, 10:53 Uhr</td>
</tr><tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l290" >Zeile 290:</td>
<td colspan="2" class="diff-lineno">Zeile 290:</td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>In Beispiel 1 muss nicht synchronisiert werden, da die Daten auf die Platte geschrieben werden.</div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>In Beispiel 1 muss nicht synchronisiert werden, da die Daten auf die Platte geschrieben werden.</div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>Es macht dort keinen Unterschieb ob der Thread unterbrochen wird oder nicht.</div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>Es macht dort keinen Unterschieb ob der Thread unterbrochen wird oder nicht.</div></td></tr>
<tr><td class='diff-marker'>−</td><td style="color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;"><div>{{<del class="diffchange diffchange-inline">Hinweis</del>|VORSICHT! Wenn jetzt die gesamten Threads aber mit ein und dem selben FileStream arbeiten dann muss auch synchronisiert werden.</div></td><td class='diff-marker'>+</td><td style="color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;"><div>{{<ins class="diffchange diffchange-inline">Warnung</ins>|VORSICHT! Wenn jetzt die gesamten Threads aber mit ein und dem selben FileStream arbeiten dann muss auch synchronisiert werden.</div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>Da dieser FileStream sozusagen ein und den selbe Speicherbereich darstellt!}}<br></div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>Da dieser FileStream sozusagen ein und den selbe Speicherbereich darstellt!}}<br></div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div><br></div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div><br></div></td></tr>
</table>Flashhttps://wiki.delphigl.com/index.php?title=Tutorial_Multithreading&diff=22954&oldid=prevDGLBot: Der Ausdruck ''<pascal>(.*?)</pascal>'' wurde ersetzt mit ''<source lang="pascal">$1</source>''.2009-03-10T18:13:23Z<p>Der Ausdruck ''<pascal>(.*?)</pascal>'' wurde ersetzt mit ''<source lang="pascal">$1</source>''.</p>
<a href="https://wiki.delphigl.com/index.php?title=Tutorial_Multithreading&diff=22954&oldid=20214">Änderungen zeigen</a>DGLBothttps://wiki.delphigl.com/index.php?title=Tutorial_Multithreading&diff=20214&oldid=prevI0n0s: /* Wie kann ich sie denn erstellen? */2007-02-20T10:54:20Z<p><span dir="auto"><span class="autocomment">Wie kann ich sie denn erstellen?</span></span></p>
<table class="diff diff-contentalign-left" data-mw="interface">
<col class='diff-marker' />
<col class='diff-content' />
<col class='diff-marker' />
<col class='diff-content' />
<tr style='vertical-align: top;' lang='de'>
<td colspan='2' style="background-color: white; color:black; text-align: center;">← Nächstältere Version</td>
<td colspan='2' style="background-color: white; color:black; text-align: center;">Version vom 20. Februar 2007, 10:54 Uhr</td>
</tr><tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l59" >Zeile 59:</td>
<td colspan="2" class="diff-lineno">Zeile 59:</td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>type</div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>type</div></td></tr>
<tr><td class='diff-marker'>−</td><td style="color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;"><div>   TMyOwnThread = class(<del class="diffchange diffchange-inline">Thread</del>)</div></td><td class='diff-marker'>+</td><td style="color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;"><div>   TMyOwnThread = class(<ins class="diffchange diffchange-inline">TThread</ins>)</div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>   protected</div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>   protected</div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>     procedure Execute; override;</div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>     procedure Execute; override;</div></td></tr>
</table>I0n0shttps://wiki.delphigl.com/index.php?title=Tutorial_Multithreading&diff=15875&oldid=prevMyChaOS am 26. Dezember 2005 um 11:15 Uhr2005-12-26T11:15:22Z<p></p>
<table class="diff diff-contentalign-left" data-mw="interface">
<col class='diff-marker' />
<col class='diff-content' />
<col class='diff-marker' />
<col class='diff-content' />
<tr style='vertical-align: top;' lang='de'>
<td colspan='2' style="background-color: white; color:black; text-align: center;">← Nächstältere Version</td>
<td colspan='2' style="background-color: white; color:black; text-align: center;">Version vom 26. Dezember 2005, 11:15 Uhr</td>
</tr><tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l442" >Zeile 442:</td>
<td colspan="2" class="diff-lineno">Zeile 442:</td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>[[Benutzer:Lossy eX|Lossy eX]]</div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>[[Benutzer:Lossy eX|Lossy eX]]</div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"></td></tr>
<tr><td class='diff-marker'>−</td><td style="color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;"><div>{{TUTORIAL_NAVIGATION|<del class="diffchange diffchange-inline">-</del>|-}}</div></td><td class='diff-marker'>+</td><td style="color:black; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;"><div>{{TUTORIAL_NAVIGATION| <ins class="diffchange diffchange-inline">[[Tutorial Komponentenentwicklung]] </ins>| <ins class="diffchange diffchange-inline">[[Tutorial Software</ins>-<ins class="diffchange diffchange-inline">Synthesizer]]</ins>}}</div></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"></td></tr>
<tr><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>[[Kategorie:Tutorial|Multithreading]]</div></td><td class='diff-marker'> </td><td style="background-color: #f9f9f9; color: #333333; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #e6e6e6; vertical-align: top; white-space: pre-wrap;"><div>[[Kategorie:Tutorial|Multithreading]]</div></td></tr>
</table>MyChaOShttps://wiki.delphigl.com/index.php?title=Tutorial_Multithreading&diff=11966&oldid=prev2005-10-29T16:11:26Z<p></p>
<p><b>Neue Seite</b></p><div>=Multithreading=<br />
<br />
==Vorwort==<br />
<br />
In diesem (meinem ersten) Tutorial versuche ich euch etwas in das Mysterium der Threads einzuweihen und<br />
ich werde versuchen euch die Leistungsfähigkeit und die Gefahren etwas näher zu bringen.<br><br />
<br />
==Was ist ein Thread?==<br />
<br />
Als erstes sollten wir einmal klären was ein Thread überhaupt ist. <br><br />
Ein Thread ist die Möglichkeit mehrere Quellcodes zur "gleichen Zeit" ausführen zu können.<br><br />
Hier haben wir schon Trugschluss Nummer eins! In echt werden diese Quellen nicht zur gleichen Zeit ausgeführt.<br />
Sie werden abwechselnd aufgerufen. Dies geschieht allerdings so schnell, dass man es mit bloßem Auge nicht sehen kann.<br><br />
<br><br />
Nehmen wir mal an wir haben 2 Threads in einer Anwendung und beide führen große Berechnungen aus. <br />
Dann wird es so aussehen, dass Thread 1 anfängt etwas zu berechnen und dann (nach wenigen Millisekunden) <br />
wird er vom Betriebssystem unterbrochen und Thread 2 darf etwas rechnen. Dieser wird dann auch wieder unterbrochen und Thread 1 darf wieder.<br />
So geht das weiter bis nichts zu berechnen mehr übrig ist.<br><br />
<br><br />
Neben dem Multithreading gibt es noch das sogenannt Multiprocessing.<br><br />
Der Unterschied zum Threading ist der, dass wir mehrere Anwendungen haben und somit auch vollkommen unterschiedliche Speicherbereiche.<br><br />
Das ist beim Multithreading nicht der Fall, da wir ja nur eine Anwendung haben. <br />
Und aus den gleichen Speicherbereichen und den Unterbrechungen resultiert die Notwendigkeit, <br />
dass wir die Threads untereinander synchronisieren müssen (siehe Abschnitt [[Tutorial_Multithreading#Synchronschwimmen|Synchronschwimmen]]).<br><br />
<br><br />
Der Richtigkeit halber sollte hier erwähnt werden, dass zu Beginn einer jeden Anwendung schon ein eigenen Thread verfügbar ist <br />
(sonst würde sie ja nicht funktionieren). Dieser nennt sich VCL-Thread und arbeitet Windowsbotschaften (etc.) ab.<br />
So gesehen ist er die Mutter für alles. Von diesem Thread sieht der Entwickler so gut wie gar nichts. Aber es ist da!<br><br />
<br />
==Wozu brauche ich Threads denn überhaupt?==<br />
<br />
Das typische Einsatzgebiet von Threads ist überall dort wo:<br />
<br><br />
#mehrere Quellen gleichzeitig ausgeführt werden müssen.<br />
#:z.B.: Wenn man Spiel programmiert in dem im Hintergrund schon ein neuer Level geladen wird. Das beste Beispiel dafür ist Half-Life.<br />
#:Am Ende eines Levels erscheint ein Schriftzug "Loading" und es ruckelt dann ein wenig. <br />
#:Und genau zu diesem Zeitpunkt wird im Hintergrund ein neuer Level geladen. So hat man den Eindruck es handele sich dabei um ein riesiges Level.<br />
#wo man auf Hardware warten muss (Modem)<br />
#:z.B.: ein Webspider. Er lädt Webseiten auf die Festplatte. Dort muss die Anwendung größtenteils auf die Server warten.<br />
#:An dieser Stelle haben Threads 2 große Vorteile:<br />
#:# Solange auf den Server gewartet werden muss blockiert die Anwendung nicht (da nur der eine Thread wartet).<br />
#:# Und der wohl größere Vorteil. Es wird ermöglicht, mehrere Dateien gleichzeitig herunter zu laden. <br />
#:#:So kann man in kürzerer Zeit und effizienterer Nutzung der Internetanbindung (DSL, ...) eine Webseite herunter laden.<br />
<br><br />
Das waren allerdings nur 2 Beispiele von vielen. Es ist der Kreativität eines Entwicklers freien Lauf gelassen.<br><br />
Desweiten ermöglichen es Threads auch mehrere im System vorhandene Prozessoren anzusprechen.<br />
Und somit zum Beispiel auf dem einen Prozessor zu Rendern und auf dem anderen die notwendigen Berechnungen durchzuführen.<br><br />
<br />
==Wie kann ich sie denn erstellen?==<br />
<br />
Das Erstellen eines Thread ist in Delphi wahnsinnig einfach. *eg* <br> <br />
Das Einzige was wir tun müssen ist die Klasse TThread abzuleiten und die Methode Execute zu überschreiben.<br><br />
Wer keine Ahnung von Objekt orientiertem Programmieren (OOP) hat sollte sich an dieser Stelle erst einmal darüber schlau machen.<br><br />
<br />
<pascal>interface<br />
<br />
uses<br />
classes;<br />
<br />
type<br />
TMyOwnThread = class(Thread)<br />
protected<br />
procedure Execute; override;<br />
end;<br />
<br />
implementation<br />
<br />
procedure TMyOwnThread.Execute;<br />
begin<br />
// Führe hier irgendwelche Berechnungen aus.<br />
end;</pascal><br />
<br />
Das war es. <br><br />
Ihr werdet euch jetzt Fragen was daran so kompliziert ist!<br><br />
Bisher noch nichts! Das Komplizierte kommt erst jetzt. <br />
Wenn ihr zum Beispiel in dem Execute einen Fehler gemacht habt (Was man nie ausschließen kann) oder ihr versucht eine Datei zu öffnen die aber nicht existiert.<br />
Dann wird von Delphi (zu Recht) eine Exception ausgelöst. Normalerweise wird diese von der Anwendung abgefangen und als Fehlermeldung (mit rotem Ausrufezeichen) ausgegeben.<br />
Das hat bestimmt schon jeder einmal gesehen. Aber in einem Thread haben wir ein anderes Verhalten. Hier wird dieser Fehler spätestens vom Betriebssystem abgefangen. <br />
Und das Betriebssystem terminiert zum Dank dann eure Anwendung mit dem Fehler "Unknow Software Error". Das tollste an der Sache ist aber. <br />
Sobald die Anwendung aus Delphi heraus gestartet wird fängt Delphi diesen Fehler ab. Allerdings liefert Delphi dann KEINEN Fehler. <br />
Es kommt nicht einmal eine Warnung! Es kommt gar nichts! Die Anwendung läuft ohne Fehler weiter. <br />
Und sobald sie außerhalb von Delphi gestartet wird, wird sie vom Betriebssystem abgeschossen.<br><br />
<br><br />
Wie kann ich dem vorbeugen?<br><br />
Und zwar in dem ich die Exceptions abfange. Auch wenn ich sie dann einfach nur ignoriere (was natürlich dreckig wäre) aber abfangen muss ich sie.<br><br />
<br />
<pascal>procedure TMyOwnThread.Execute;<br />
begin<br />
try<br />
// Führe hier irgendwelche Berechnungen aus.<br />
except<br />
on e: exception do begin<br />
// mache hier irgendetwas mit dem Fehler.<br />
end;<br />
end;<br />
end;</pascal><br />
<br />
Soviel zur Vorbereitung. Jetzt wollen wir den Thread aber auch ausführen. <br />
Dazu brauchen wir zu erst eine Methode in der wir diesen Thread erzeugen können. Nehmen wir mal das Event Form1.OnCreate.<br><br />
<br />
<pascal>procedure TForm1.FormCreate(Sender: TObject);<br />
var<br />
Thread: TMyOwnThread;<br />
begin<br />
Thread := TMyOwnThread.Create(True);<br />
// Der Parameter heißt CreateSuspended.<br />
// Er hat zur Folge wenn wir ein false übergeben,<br />
// dass der Thread sofort anfängt mit arbeiten.<br />
// meist haben wir ihm dann aber noch gar keine Daten übergeben.<br />
// also rufen wir ihm Suspended auf<br />
<br />
Thread.FreeOnTerminate := True;<br />
// FreeOnTerminate bedeutet sobald der Thread die Procedure Execute<br />
// verlassen hat wird sein Speicher wieder von alleine Frei gegeben.<br />
// andernfalls müsste später im Programm Thread.Free aufgerufen werden.<br />
<br />
Thread.Resume;<br />
// Falls der Thread suspended gestartet wurde sorgt dies dafür,<br />
// dass er anfängt mit arbeiten.<br />
<br />
Thread.Suspend;<br />
// Dies sorgt dafür, dass die Arbeit des Threads pausiert wird.<br />
// Weiteführung durch Resume.<br />
<br />
Thread.Terminate;<br />
// Hierbei handelt es sich nicht um eine Methode die dafür sorgt,<br />
// dass der Thread aufhört zu arbeiten. Sondern sie setzt eine Variable<br />
// (FTerminated) in der Basisklasse.<br />
// Ein Thread muss auf diese Methode selber reagieren.<br />
// Wenn in Execute Berechnungen in einer Schleife durchgeführt werden,<br />
// dann muss die Property Terminated abgefragt werden<br />
// und wenn diese gesetzt ist, dann sollte die arbeit<br />
// normal beendet werden und die Methode verlassen werden.<br />
end;</pascal><br />
<br />
Das war es eigentlich schon soweit zum Thema Thread erstellen. Ach ja noch eines. <br />
Der Thread ist ansonsten genau dasselbe wie jede andere Klasse auch. <br />
Sprich er kann genau so erweitert werden wie das Form1 oder sonst irgendeine Klasse.<br><br />
<br />
==Synchronschwimmen==<br />
(oder auch wo bin ich) ...<br />
<br><br />
Das wohl komplizierteste an Multithreading ist zu wissen in welchem Thread (Kontext) eine Methode aufgerufen wird <br />
und wann bzw. was man sie synchronisieren oder sie schützen sollte.<br />
In diesem Abschnitt versuche ich das euch einmal näher zu erklären.<br><br />
<br><br />
Um schon einmal den wohl am häufigst gemachten Irrtum aus der Welt zu schaffen:<br><br />
Ein Thread existiert erst genau dann, wenn die Execute Methode aufgerufen wurde. <br />
Und dort meine ich nicht, dass man irgendwo im Quelltext Thread.Execute stehen hat.<br />
Nein ich meine den resultierenden Aufruf vom Betriebssystem auf Thread.Resume.<br />
Da das bestimmt ein wenig unverständlich war hier mal ein paar Beispiele aus dem FormCreate (VCL-Thread):<br><br />
<br />
<pascal>Thread := Thread.Create(True);<br />
// Der Konstructor wird aus dem VCL-Thread aufgerufen<br />
// es existiert noch kein Thread!<br />
<br />
Thread.Resume;<br />
// Hiermit wird der Thread angeworfen und sobald<br />
// die erste Zeile von Execute aufgerufen<br />
// wurde existiert der Thread<br />
<br />
Thread.Suspend;<br />
// Es handelt sich zwar hier um eine Methode der Threadklasse<br />
// allerdings wird sie im Kontext vom VCL-Thread aufgerufen.<br />
// Der Thread wird hierbei aber vom Betriebssystem angehalten.<br />
// Resume kann diesen dann jederzeit wieder starten.<br />
<br />
Thread.Terminate;<br />
// Genau da Selbe wie bei Suspend allerdings wird er hierdurch<br />
// nicht angehalten.<br />
// sondern es wird die schon besprochene Variable gesetzt.<br />
<br />
Thread.Execute;<br />
// Die Execute Methode wurde nicht vom Betriebsssytem aufgerufen<br />
// und wird somit im Kontext vom VCL-Thread aufgerufen.<br />
// Es existiert KEIN Thread.<br />
<br />
// Das funktioniert allerdings nur sofern diese als Public<br />
// überschrieben wurde. Was natürlich nicht gemacht werden sollte!!!</pascal><br />
<br />
Jetzt mal ein Beispiel aus dem Thread.Execute:<br><br />
<br />
<pascal>Thread.Terminate;<br />
// Diese Methode wurde nun aus dem Kontext vom den Thread<br />
// aufgerufen und setzt die bekannte Variable.<br />
<br />
Form1.Button1OnClick();<br />
// Hierbei handelt es sich zwar um eine Methode aus dem VCL-Thread<br />
// (Form1) aber sie wird im Kontext von unserem Thread aufgerufen.<br />
// Es spielt überhaupt keine Rolle wie die Methode heißt oder<br />
// was sie macht. Es könnte sich hierbei auch um einen Callback<br />
// vom Thread handeln.</pascal><br />
<br />
{{Hinweis|Alles was aus dem Execute aufgerufen wird hat als Kontext diesen Thread!}}<br><br />
<br />
Jetzt stellt sich natürlich die Frage was muss alles synchronisiert werden?<br><br />
Pauschal lässt sich nur sagen, alles was irgendwo von mehreren Threads (incl. VCL-Thread) geschrieben werden kann muss synchronisiert werden.<br />
Ich erkläre euch das mal besser an einem kleinem Beispiel:<br><br />
<br><br />
Nehmen wir einmal an wir haben einen Webspider. Dieser soll 200 Webseiten downloaden.<br><br />
Wir erstellen uns einen Thread dem ich die URL übergebe und der selbständig mit dem Laden anfängt.<br />
Wenn dieser fertig ist dann sagt er dem VCL-Thread mittels eines Events, dass er fertig ist. Aussehen würde das so:<br><br />
<br />
<pascal><br />
type<br />
TBinFertig = procedure(const Content: String) of object;<br />
<br />
TMyOwnThread = class(TThread)<br />
private<br />
FBinFertig: TBinFertig;<br />
procedure SyncBinFertig;<br />
public<br />
property BinFertig: TBinFertig read FBinFertig write FBinFertig;<br />
end;<br />
<br />
implementation<br />
<br />
procedure TMyOwnThread.SyncBinFertig;<br />
begin<br />
if Assigned(FBinFertig)<br />
then FBinFertig(DasIstDerInhaltDerWebseite);<br />
end;<br />
<br />
procedure TMyOwnThread.Execute;<br />
begin<br />
try<br />
// Download der Seite ...<br />
<br />
// Synchronisieren<br />
Synchronize(SyncBinFertig);<br />
except<br />
on e: exception do begin<br />
// mache hier irgendetwas mit dem Fehler.<br />
end;<br />
end;<br />
end;</pascal><br />
<br />
So lasst das Stück Quelle einmal auf euch wirken.<br><br />
Ich erkläre in der Zeit was genau wo passiert und warum das so aussieht.<br />
TBinFertig ist die Definition einer Objektmethode.<br />
Das ermöglicht es Proceduren als Variablen zu speichern.<br />
Siehe Button.OnClick oder die ganzen anderen Events.<br />
Dieser wird dann als Eigenschaft (Property) nach außen geführt.<br />
Somit können andere Klassen darauf zugreifen.<br><br />
<br><br />
In dem Execute wird jetzt mit Hilfe von Synchronize die Methode SyncBinFertig aufgerufen.<br />
Wir haben dort eine zusätzliche Methode weil Synchronize nur Proceduren ohne Parameter akzeptiert.<br />
Was macht Synchronize? Synchronize sorgt dafür, dass der VCL-Thread die Methode SyncBinFertig aufruft.<br />
Und wie ihr euch denken werdet ist somit der Kontext von unserem Thread auf den VCL-Thread umgeleitet worden.<br />
Synchronize macht aber noch etwas. Und zwar wartet es so lange bis die Methode SyncBinFertig zu Ende ausgeführt wurde.<br />
Das hat zu bedeuten, dass unser Thread für diesen Zeitraum stehen bleibt. Danach geht alles wie gewohnt weiter.<br />
Allerdings muss man darauf achten, dass der VCL-Thread zu diesem Zeitpunkt nichts zu tun hat.<br />
Das soll bedeuten sobald der VCL-Thread in einer Methode steckt (Button1OnClick) wird kein Synchronize abgearbeitet!<br />
Das kann zu sogenannten Deadlocks führen. Dazu aber unten mehr.<br><br />
<br><br />
Warum müssen wir an dieser Stelle synchronisieren?<br />
Wir müssen an dieser Stelle nicht immer synchronisieren. Wir müssen nur dann synchronisieren, wenn wir das Ergebnis in einen,<br />
vom einem Thread (VCL-Thread oder anderer) verwalteten Speicherbereich, schreiben wollen!<br />
Hier einmal zwei Beispiele die das etwas veranschaulichen sollen:<br><br />
<br />
Beispiel 1 in dem wir '''nicht''' synchronisieren müssen.<br><br />
<br />
<pascal>procedure Form1.BinFertig(const Content: String);<br />
var<br />
FS: TFileStream;<br />
begin<br />
FS := TFileStream.Create(FileName, fmCreate);<br />
FS.Write(Content[1], Length(Content));<br />
FS.Free;<br />
end;</pascal><br />
<br />
Beispiel 2 in dem wir synchronisieren müssen.<br><br />
<br />
<pascal>procedure Form1.BinFertig(const Content: String);<br />
begin<br />
Form1.Buffer := Form1.Buffer + Content;<br />
end;</pascal><br />
<br />
Warum müssen wir Beispiel 2 synchronisieren und Beispiel 1 nicht?<br><br />
Ihr erinnert euch bestimmt an das was ich oben gesagt hatte?<br><br />
Nein. Macht nichts. Und zwar sagt ich, dass Thread 1 mit seinen Berechnungen unterbrochen wird und Thread 2 etwas machen darf.<br />
So was ist wenn jetzt 2 von den 5 Threads gleichzeitig fertig geworden sind?<br />
Thread 1 ruft BinFertig auf. Diese Methode will nun den Content ihrer Seite an den bereits vorhandenen anhängen.<br />
Der Thread ist zu 50% mit der Arbeit fertig und nun ist Thread 2 an der Reihe!<br />
Auch er hängt jetzt ein bisschen was an den vorhandenen an. Jetzt die Frage was kommt im Endeffekt dabei raus?<br />
Ich weiß es auch nicht! Ich schätze aber mal, dass entweder totaler Datenmüll oder ein Absturz dabei raus kommt.<br />
Und genau aus diesem Grund muss hier synchronisiert werden.<br />
So kann nur maximal 1 Thread etwas an dem Buffer anhängen und die anderen müssen warten!<br><br />
<br />
In Beispiel 1 muss nicht synchronisiert werden, da die Daten auf die Platte geschrieben werden.<br />
Es macht dort keinen Unterschieb ob der Thread unterbrochen wird oder nicht.<br />
{{Hinweis|VORSICHT! Wenn jetzt die gesamten Threads aber mit ein und dem selben FileStream arbeiten dann muss auch synchronisiert werden.<br />
Da dieser FileStream sozusagen ein und den selbe Speicherbereich darstellt!}}<br><br />
<br><br />
Ein anderer Fall in dem Synchronisiert werden muss ist, wenn ein Thread gerade etwas lesen möchte und ein anderer etwas genau dort hin schreiben will.<br />
In diesem Fall kann genau das selbe passieren wie in Beispiel 2 wenn nicht synchronisiert wird.<br><br />
<br />
==Speicherbereiche schützen==<br />
<br />
Eine andere Möglichkeit etwas zu schützen sind sogenannte '''Critical Sections''' (zu gut Deutsch: kritische Bereiche).<br> <br />
Der Buffer in Beispiel 2 wäre ein solcher Bereich. Critical Sections ermöglichen es dem Entwickler einen Bereich gegen andere Threads zu sichern.<br />
Also Erstes brauchen wir eine Membervariable in Form1. Diese muss das z.B.: im OnCreate erzeugt werden:<br><br />
<br />
<pascal>uses<br />
..., SyncObjs;<br />
<br />
TForm1 = class(TForm)<br />
...<br />
private<br />
FBufferCritSect: TCriticalSection;<br />
...<br />
end;</pascal><br />
<br />
Anschließend sieht unser BinFertig so aus:<br><br />
<br />
<pascal>procedure Form1.BinFertig(const Content: String);<br />
begin<br />
FBufferCritSect.Enter;<br />
Form1.Buffer := Form1.Buffer + Content;<br />
FBufferCritSect.Leave;<br />
end;</pascal><br />
<br />
Nun brauchen wir unser BinFertig auch nicht mehr synchronisieren.<br><br />
Warum den das jetzt?<br><br />
Die CriticalSection die wir eingesetzt haben würde dafür sorgen, dass maximal 1 Thread sich in dem Block zwischen Enter und Leave aufhalten würde.<br />
Alle anderen müssten dann warten bis sie an der Reihe sind.<br />
Dadurch, dass wir nicht mehr synchronisieren müssen, würde der Kontext wieder bei den einzelnen Thread bleiben.<br />
Unser VCL-Thread würde also mit einer Critical Section nicht zu tun haben. (er würde Däumchen drehen) <br />
Dafür könnte er sich aber voll und ganz um dem Rest kümmern können. <br />
Um zu vermeiden, dass bei einem Fehler die Critical Section für immer und ewig verschlossen bleibt (Deadlock) sollte man einen Ressourcenschutzblock um sie herum errichten.<br />
Das soll bedeuten. Auch im Fehlerfall würde eine gewisse Aktion IMMER ausgeführt werden. Unsere Exception wird dadurch aber nicht abgefangen!<br />
Sie wird ganz normal weitergeleitet.<br><br />
<br />
<pascal>procedure Form1.BinFertig(const Content: String);<br />
begin<br />
FBufferCritSect.Enter;<br />
try<br />
Form1.Buffer := Form1.Buffer + Content;<br />
finally<br />
// Egal was auch passiert die Section würde immer verlassen werden<br />
// Sofern die Section und die Anwendung noch existieren, und<br />
// der Rechner noch Läuft. ;)<br />
FBufferCritSect.Leave;<br />
end;<br />
end;</pascal><br />
<br />
==Was ist denn das schon wieder?==<br />
<br />
Was wäre das Leben wenn das bisher schon alle Probleme waren? Einfach langweilig ist weiß!<br><br />
Also hier noch einmal ein nicht unbedingt triviales Problem:<br> <br />
Was ist passiert wenn meine Anwendung auf einmal ohne ehrsichtlich Grund stehen bleibt?<br />
Also es tritt kein Fehler auf. Nein. Sie bleibt einfach stehe und ich kann nichts dagegen unternehmen.<br><br />
<br><br />
Das was man sich dann eingefangen hat ist Deadlock. Das Programm wartet an 2 Stellen darauf, dass sich jeweils die andere fertig wird.<br />
Ein hoffentlich einfaches Beispiel:<br><br />
Wir haben einen Thread der besitzt eine Methode. <br />
In dieser Methode wartet er darauf, dass der Thread etwas ganz bestimmtes getan hat und kehrt dann zurück. <br />
Wenn diese Methode jetzt aus dem VCL-Thread aufgerufen und in diesem Thread, in der Zeit wären der VCL-Thread auf das Rückkehren der Methode wartet,<br />
ein Synchronize aufgerufen wird, dann ergibt das einen Deadlock. Die Erklärung ist einfach:<br> <br />
Das Synchronize wartete ja darauf, dass der VCL-Thread es abarbeiten kann. Wenn dieser aber selber wartet, dann bleiben beide stehen.<br><br />
<br><br />
Ein anderes Beispiel haben wir im Abschnitt darüber kennen gelernt. <br />
Und zwar wenn durch einen Fehler die Critical Section nicht wieder geöffnet wurde.<br><br />
<br />
==Prioritäten==<br />
<br />
Das Setzen von Threadprioritäten geschieht über die Eigenschaft Priority eines Threads.<br><br />
Was ist das Hinterlistige an den Prioritäten? Oder anders gesagt worauf sollte man achten, wenn man Prioritäten einstellt?<br><br />
Standardmäßig wird von jedem Thread oder Prozess die Priorität Normal verwendet. <br />
Wenn ich nun aber einen Thread habe der eine höhere Priorität erhalten soll dann muss ich darauf achten,<br />
dass er noch genügend Ressourcen für die Anderen übrig lässt. Das soll mal an einem kleinen Beispiel erläutert werden:<br><br />
Wir haben einen Thread der besonders wichtige Berechnungen durchführt. Diese sollen sehr zeitkritisch also mit hoher Priorität abgearbeitet werden.<br />
In dem Execute muss ich also nachschauen ob ich eine Berechnung vorliegen habe:<br><br />
<br />
<pascal>while (not Terminated) do begin<br />
if (BerechnungDa) then begin<br />
// berechne<br />
end;<br />
end;</pascal><br />
<br />
An und für sich wäre das ja schon die Lösung des Problems. Allerdings dadurch, dass ich permanent nachschaue ob ich eine Berechnung da habe,<br />
würde dieser Thread so viel an CPU klauen, dass für die anderen nichts mehr übrig bliebe.<br />
Wenn ich die Priorität auf Echtzeit gesetzt habe kann es sogar sein, dass Windows stehen bleibt und sich nur noch auf das konzentriert.<br />
Eine Lösung für dieses Problem wäre wenn ich nur ein paar mal in der Sekunde nachschauen würde:<br><br />
<br />
<pascal>while (not Terminated) do begin<br />
if (BerechnungDa) then begin<br />
// berechne<br />
end else begin<br />
sleep(10);<br />
end;<br />
end;</pascal><br />
<br />
Das würde dafür sorgen, dass nur 100mal in der Sekunde abgefragt werden würde. <br />
Diese Auflösung würde schon für die meisten Sachen vollkommen ausreichen aber wir können noch einen drauf setzen. <br />
Das hätte dann eine noch höhere Auflösung bei weniger Aufwand (Abfragen in der Sekunde) von unserer Seite. <br />
Das Ganze würde dann mit Hilfe eines Events arbeiten. Events sind in der Unit SyncObjs deklariert.<br><br />
<br />
<pascal>Event := TEvent.Create(nil, true, false, '');</pascal><br />
<br />
So wird ein Event erzeugt. Der erste Parameter ist ein Pointer auf ein SecureAttribut. <br />
Das ist in den meisten Fällen (bei mir immer ;) ) nil. Viel interessante sind die anderen drei. <br />
Der zweite Parameter gibt an ob das Event sich von alleine wieder zurücksetzen kann. <br />
Der Vorteil davon, jedes Mal wenn dieses Event ausgelöst wird kann immer nur einer der darauf wartenden Parteien (Threads) das Signal bekommen.<br />
Der Dritte ist der Initialisierungsstatus. Sprich ob es zu Begin gleich gesetzt ist oder lieber doch nicht. <br />
Der Vierte ist wieder sehr geil. Dieser ist der Name des Events. Warum zu Teufel brauch ein Event einen Namen? <br />
Ganz einfach, wenn es von mehren Anwendungen gleichzeitig abgefragt bzw. gesetzt werden soll dann muss es ja eindeutig identifizierbar sein. <br />
Und genau das macht der Name. Ihr erzeugt euch zwei Events mit dem Selben Namen und schon könnt ihr andere Anwendungen damit triggern. :) <br />
Aber wieder zurück zum Thema. So würde unsere Quelle mit einem Event ausschauen:<br><br />
<br />
<pascal>while (not Terminated) do begin<br />
if (Event.WaitFor(100) = wrSignaled) then begin<br />
// Sofern sich das Event nicht von alleine zurücksetzt müssen wir noch<br />
// Event.ResetEvent aufrufen.<br />
<br />
// Berechne was<br />
end;<br />
end;</pascal><br />
<br />
In der Methode die uns normalerweise die Berechnung übergeben würde müssen wir nur noch das Event auslösen (Event.SetEvent) und schon klappt alles.<br />
Was passiert bei WaitFor? WaitFor ist so konzipiert, dass es 100 Millisekunden warten würde und wenn das Event immer noch nicht ausgelöst wurde,<br />
dann würde es mit wrTimeOut zurückkommen. Wenn dieses Event jetzt allerdings nach 10 Millisekunden schon ausgelöst wurde,<br />
dann wartet WaitFor logischerweise nicht noch 90 Millisekunden. <br />
Nein. Es kehrt sofort wieder zurück und als Rückgabewert würden wir wrSignaled bekommen. <br />
Warum habe ich jetzt allerdings 100Millisekunden verwendet und nicht etwa unendlich (INFINITE) oder 15 Minuten?<br />
Na weil wir ja sonst nicht wüssten wann unser Thread terminiert wurde! So wird 10 mal in der Sekunde abgeprüft ob er terminiert wurde.<br><br />
<br />
==Schlusswort==<br />
<br />
Wenn man sich einen Thread erzeugen will der in einem Fenster OpenGL rendert, dann darf (sollte) man dort so gut wie gar nichts synchronisieren.<br />
Allerdings wenn man etwas synchronisiert, dann MUSS alles synchronisiert werden. Also ich denke jetzt an das Initialisieren, das Rendern und Löschen des Renderkontext.<br />
Und wenn man alles synchronisiert, dann kann man auch komplett auf den Thread verzichten. Weil dadurch alles im VCL-Thread ausgeführt wird.<br />
Und das ist ja eigentlich nicht Sinn und Zweck davon.<br><br />
<br><br />
Also Merke! Nur das Notwendigste synchronisieren oder versuchen ganz auf den Thread zu verzichten.<br />
Und das Notwendigste ist alles das wo sich Überschneidungen beim Schreiben ergeben können. <br />
Lesen alleine ist ungefährlich, weil dort ja keine Daten verändert werden.<br><br />
<br />
In diesem Sinne.<br><br />
<br><br />
Euer<br><br />
[[Benutzer:Lossy eX|Lossy eX]]<br />
<br />
{{TUTORIAL_NAVIGATION|-|-}}<br />
<br />
[[Kategorie:Tutorial|Multithreading]]</div>Flash