Home » Tutorials » Object Pascal/RTL » Threads

Threads

Beispielprogramm

Um das viele Hintergrundwissen, das für den Einsatz von Threads erforderlich ist, ein wenig zu veranschaulichen, hat Borland ein Beispielprojekt erstellt, das im Unterverzeichnis DemosThreads zu finden ist.
Öffnen Sie das Projekt thrddemo.dpr, dann können wir kurz den Aufbau betrachten. Als erstes können Sie das Programm jedoch einmal laufen lassen, um zu sehen, was überhaupt passiert. Sie sehen ein Fenster, das in drei Spalten aufgeteilt ist. In jeder befinden sich unterschiedlich lange rote Balken, völlig unsortiert. Wie Sie im Code sehen können, repräsentieren diese Balken 115 Integer-Zahlen zwischen 0 und 169. Beim Klick auf den Button sollen diese Zahlen, die sich jeweils in einem Array befinden, sortiert werden und zwar nach unterschiedlichen Sortierverfahren.
Probieren Sie es aus, welcher Sortieralgorithmus am schnellsten arbeitet! Sie sollten dann in etwa folgendes Ergebnis erhalten, wobei QuickSort am schnellsten fertig ist:

Da die Zahlen zufällig erzeugt werden, werden sich die Grafiken nach jedem Programmstart leicht unterscheiden. Wie beim Programmablauf zu sehen ist, laufen die drei Sortierverfahren gleichzeitig und nicht nacheinander ab. Hier sind also mehrere Threads im Einsatz. Genau gesagt sind es vier: der primäre Thread sowie für jedes der drei Sortierverfahren ein zusätzlicher.
Das Programm besteht aus zwei Units. ThSort enthält den Code zur Erzeugung der Zufallszahlen, zur Anzeige und zur Verwaltung der Threads. In SortThds sind die Thread-Klassen deklariert. Außerdem finden sich hier die drei eingesetzten Sortieralgorithmen.

Die Sortier-Threads

Werfen wir nun einen Blick auf die Threads, also die Unit SortThds. Da sich die drei Threads lediglich durch den verwendeten Sortieralgorithmus unterscheiden und ansonsten gleich sind, ist das ein Fall für Vererbung. Die Klasse TSortThread ist zunächst einmal direkter Nachkomme von TThread. Wie im letzten Abschnitt erläutert, kann TThread ja nicht direkt verwendet werden. Stattdessen muss die Klasse konkretisiert werden. In diesem Fall wird der Konstruktor Create überschrieben, um den Threads Zeiger auf das mitzugeben, mit dem sie zu arbeiten haben: eine Paintbox für die Anzeige und das Array, in dem sich die Zahlen befinden. (Objektreferenzen und var-Parameter sind immer Zeiger auf einen Speicherbereich.) Im Implementation-Teil ist zu sehen, dass im Kunstruktor die privaten Felder initialisiert werden.
Im protected-Abschnitt sind drei Methoden deklariert: Execute, die – wie wir wissen – immer überschrieben werden muss und in diesem Fall einfach Sort aufruft; VisualSwap, eine Methode, die für die grafische Ausgabe zuständig ist, und da aus einem Thread heraus bekanntlich nicht direkt auf VCL-Teile (die Paintbox) zugegriffen werden darf, macht sie Gebrauch von Synchronize. Dadurch wird die DoVisualSwap-Methode aufgerufen, die vom primären Thread ausgeführt wird, damit es nicht zu Konflikten kommt. Und schließlich ist da noch die Sortier-Methode, die jedoch abstrakt deklariert wird, da der Code (logischerweise) bei jedem Sortierverfahren unterschiedlich aussieht und deshalb in den Unterklassen implementiert werden kann.
Nun folgen die drei Thread-Klassen, die letztlich direkt zum Einsatz kommen. Sie sind Nachkommen der eben beschriebenen TSortThread-Klasse und erben somit alle Methoden und Felder. Da Sort in der Oberklasse nur virtuell-abstrakt deklariert wurde, muss nun eine Implementierung folgen. Und da jede der drei Klassen für ein anderes Sortierverfahren steht, ist das auch ohne Probleme möglich.
Mehr ist zu dieser Unit eigentlich nicht zu sagen. Der Code ist recht übersichtlich und zusätzlich kommentiert, so dass man sich schnell darin zurecht finden sollte.

Der primäre Thread

Gehen wir den primären Thread so durch, wie er auch abläuft. Es beginnt mit dem OnCreate-Ereignis des Hauptfensters. Hier werden über RandomizeArrays die drei Arrays mit Zufallszahlen gefüllt. Genauer gesagt wird nur ein Array mit Zufallszahlen gefüllt. Die anderen beiden Arrays erhalten anschließend die gleichen Werte zugewiesen.
Über „Repaint“ wird ausgelöst, dass sich das Fenster neu zeichnet. Jeder der drei Paintboxen ist eine OnPaint-Ereignisbehandlungsmethode zugeordnet. BubbleSortBoxPaint für die erste, SelectionSortBoxPaint für die zweite und QuickSortBoxPaint für die dritte. Der Code in diesen drei Methoden ähnelt sich; es wird jedes Mal Paint Array aufgerufen. Nur die Parameter unterscheiden sich. Beim ersten Wert handelt es sich um die Paintbox, in der gezeichnet werden soll, und beim zweiten um das Array, das grafisch dargestellt werden soll.
So viel zu den „Vorarbeiten“. Nun wird darauf gewartet, dass der Anwender auf den Button „Start Sorting“ klickt. Dadurch wird die StartBtnClick-Methode aufgerufen. Und hier wird es nun spannend. Nachdem die Arrays noch einmal neu gefüllt werden, wird ein interner Thread-Zähler auf 3 gesetzt. Dann werden die Threads gestartet:

with TBubbleSort.Create(BubbleSortBox, BubbleSortArray) do
  OnTerminate := ThreadDone;

Durch den Aufruf des Konstruktors Create wird auch automatisch die Execute-Methode aufgerufen, so dass der Thread startet. Außerdem wird dem Thread hier noch mitgeteilt, dass er auf das OnTerminate-Ereignis mit dem Ausführen der Methode „ThreadDone“ reagieren soll. OnTerminate wird ausgelöst, sobald ein Thread fertig abgelaufen ist.
Nun laufen die Threads also, und in der Variablen ThreadsRunning steht, wie viele es sind. Ist ein Thread fertig, wird der Wert in ThreadDone um 1 reduziert. Sind alle Threads mit dem Sortieren am Ende angekommen, ist die Variable 0, und der Start-Button wird wieder verfügbar gemacht.