I. Vorteile der Multithread-Datenerfassung Neben der ansprechenden Benutzeroberfläche war die Multithreading- und Multitasking-Fähigkeit von Windows 95/98 eines der attraktivsten Merkmale. Unter Windows 95 und Windows NT kann ein Programm nicht die gesamte CPU-Ausführungszeit monopolisieren und läuft auch nicht in einer einzigen, durchgehenden Zeile. Stattdessen kann ein Programm in mehrere Programmsegmente unterteilt werden, die gleichzeitig ausgeführt werden. Diese parallel ausgeführten Programmsegmente werden als Threads bezeichnet. Unter Windows 95 und Windows NT kann das Betriebssystem mehrere Programme nacheinander ausführen – dies ist Multitasking. Die Verwendung von Multithreading für die Datenerfassung kann die Programmreaktionszeit effektiv beschleunigen und die Ausführungseffizienz steigern. Die meisten Programme müssen Benutzereingaben verarbeiten, doch die Geschwindigkeit der Benutzereingaben ist im Vergleich zur Ausführungsgeschwindigkeit der CPU extrem langsam. Dies führt dazu, dass die CPU viel Zeit mit Warten auf Benutzereingaben verschwendet (ähnlich wie unter DOS). Mit Multithreading kann ein Thread auf Benutzereingaben warten, während ein anderer die Datenverarbeitung oder andere Aufgaben durchführt. Für Datenerfassungsprogramme kann ein separater Thread für die Datenerfassung verwendet werden. Dieser Ansatz maximiert die Echtzeitleistung der Datenerfassung und ermöglicht es anderen Threads, umgehend auf Benutzeraktionen zu reagieren oder Daten zu verarbeiten. Andernfalls könnte das Programm während der Datenerfassung nicht auf Benutzeraktionen reagieren oder umgekehrt. Dies ist besonders problematisch bei großen Datensätzen und rechenintensiven Datenverarbeitungsaufgaben; ohne Multithreading wäre die lange Wartezeit während der Erfassung unerträglich. Multithreading ist jedoch deutlich komplexer als die traditionelle Programmierung. Da mehrere Threads gleichzeitig ausgeführt werden können, können viele Variablen und Daten von anderen Threads verändert werden. Dies ist die kritische Herausforderung der Thread-Synchronisierung in Multithread-Programmen. II. Zu lösende Probleme bei der Multithread-Datenerfassung Die Komplexität der Multithread-Programmierung ist vorübergehend. Bei der Verwendung von traditionellem C++ für Multithreading muss die Thread-Synchronisierung manuell gesteuert werden, was sehr komplex ist. Die Verwendung objektorientierter Designmethoden wie Delphi vereinfacht diesen Prozess jedoch. Denn Delphi übernimmt die Komplexität des Multithreadings; wir müssen lediglich die entsprechenden Klassen erben. Konkret müssen für die multithreadfähige Datenerfassung folgende Aufgaben ausgeführt werden: 1. Erstellen Sie eine Klasse `SampleThread` von der Klasse `TThread`. Diese Klasse wird für die Datenerfassung verwendet; erstellen Sie einfach während der Erfassung einen `SampleThread`. 2. Überschreiben Sie die Methode `Execute` der Oberklasse `TThread`. Diese Methode führt die Datenerfassung aus. 3. Wenn Sie die Daten während der Erfassung anzeigen möchten, schreiben Sie mehrere Prozeduren, die den Erfassungsfortschritt anzeigen und von der Methode `Execute` aufgerufen werden. Die am häufigsten verwendeten Eigenschaften/Methoden der Klasse `TThread` sind: Die Methode `Create`: `constructor Create(CreateSuspended: Boolean)`. Der Parameter `CreateSuspended` bestimmt, ob der Thread nach seiner Erstellung sofort ausgeführt wird. Bei `True` wird der neue Thread nach der Erstellung angehalten; bei `False` wird er sofort ausgeführt. Die Eigenschaft `FreeOnTerminate`: `FreeOnTerminate: Boolean`. Diese Eigenschaft legt fest, ob der Programmierer für das Beenden des Threads verantwortlich ist. Ist diese Eigenschaft auf `True` gesetzt, beendet VCL das Thread-Objekt automatisch, sobald der Thread beendet wird. Der Standardwert ist `False`. Die Eigenschaft `OnTerminate` ist vom Typ `TNotifyEvent`. Dieses Attribut spezifiziert ein Ereignis, das beim Beenden des Threads eintritt. Betrachten wir ein konkretes Beispiel. III. Implementierung der Multithread-Datenerfassung. Dies ist ein von mir entwickeltes Programm zur Messung der Funktion einer Pumpeneinheit. Es erfasst Last- und Wegdaten an den Aufhängungspunkten der Pumpeneinheit, verarbeitet diese und erstellt anschließend ein Funktionsdiagramm der Pumpeneinheit. Die Abbildung zeigt die Benutzeroberfläche während der Datenerfassung. Nach dem Klicken auf die Schaltfläche „Daten erfassen“ erstellt das Programm einen neuen Thread und legt dessen Attribute fest. Dieser neue Thread führt die Datenerfassung durch. Der Ablauf ist wie folgt: Abbildung 1 Prozedur TsampleForm.DoSampleBtnClick(Sender: TObject); Begin ReDrawBtn.Enabled := True; DoSampleBtn.Enabled := False; FFTBtn.Enabled := True; TheSampler := SampleThread.Create(False); ← Sampling-Thread erstellen TheSampler.OnTerminate := FFTBtnClick; ← Die nach dem Sampling auszuführende Aufgabe TheSampler.FreeOnTerminate := True; ← Sampling abbrechen End; Die Klassendefinition des Sampling-Threads lautet wie folgt: Type SampleThread = class(TThread) Public function AdRead(ach: byte): integer; safecall; ← Funktion zum Lesen der A/D-Karte procedure UpdateCaption; ← Sampling-Fortschritt anzeigen procedure ShowCostTime; ← Sampling-Zeit anzeigen private { Private declarations } protected thes, thep: real; dt: real; id: integer; st, ed: LongInt; procedure Execute; override; ← Dies ist entscheidend. End; Diese Klasse definiert eine Funktion `AdRead` zur Ansteuerung der A/D-Karte sowie zwei Prozeduren zur Anzeige des Erfassungsfortschritts und der verstrichenen Zeit. Beachten Sie, dass die Funktion `AdRead` in Assemblersprache geschrieben ist und der Parameteraufruf im `safecall`-Format erfolgen muss. Der Code für die überladene Schlüsselmethode `Execute` lautet wie folgt: `Procedure SampleThread.Execute; Begin StartTicker := GetTickCount; id := 0; Repeat thes := Adread(15) * ad2mv * mv2l; → Erfassung des 15. Kanals thep := Adread(3) * ad2mv * mv2n; → Erfassung des 3. Kanals dt := GetTickCount - StartTicker; sarray[id] := thes; parray[id] := thep; tarray[id] := dt; inc(id); Synchronize(UpdateCaption);` → Hinweis: Fortschritt der Datenerfassung anzeigen, bis id>=4096; ed := GetTickCount; Synchronize(ShowCostTime); → Hinweis: Zeitaufwand anzeigen; Wie aus dem obigen Code ersichtlich, unterscheidet sich Execute grundsätzlich nicht von allgemeinem Code. Der einzige Unterschied besteht darin, dass die Prozesse zur Anzeige des Erfassungsfortschritts und des Zeitaufwands nicht direkt aufgerufen werden können. Stattdessen werden sie indirekt über `Synchronize` aufgerufen. Dies dient der Synchronisierung der Prozesse. IV. Fazit Das obige Programm wurde mit Delphi 4.0 programmiert und auf einem AMD-K6-2/300 implementiert. Die Testergebnisse sind wie folgt: Mit Multithreading dauert die Erfassung von 4096 Punkten in der Regel 10–14 Sekunden; ohne Multithreading dauert es 1–1,5 Minuten. Es ist offensichtlich, dass Multithreading die Ausführungseffizienz des Programms deutlich verbessern kann. (Aus *Computer World*, Ausgabe 20, 1999 (31. Mai): Computer und Leben, Hebei Gu'an, North China Petroleum Staff University, Yuan Yilin, Li Xiaoping)