000
14.01.2003, 10:29 Uhr
void*
Generic Pointer (Operator)
|
erstellt von Stefan Dreckmann
Threads sind nützlich, wenn man etwa in einem GUI-Programm eine längere Aufgabe ausführen und trotzdem erreichen möchte, dass das Programm bedienbar bleibt. Man lagert die Aufgabe (z.B. das Durchsuchen des Dateisystems nach einer bestimmten Datei) in einen eigenen Thread aus und erreicht damit, dass der "Hauptthread" parallel zum"Arbeitsthread" weiterläuft. Der Benutzer kann während der Suche Menüs auswählen oder Buttons drücken; etwa, um die Suche abzubrechen. Der Unterschied zwischen Threads und Prozessen ist, dass Prozesse in einem eigenen Adressraum ablaufen und Threads in dem Adressraum des Prozesses,der sie gestartet hat. Das bedeutet, dass man einem Thread z.B. einfach die Adresse eines Speicherbereichs übergeben kann, in den er ein Suchergebnis schreiben soll. Würde man dieselbe Aufgabe mit einem eigenen Prozess ausführen, ginge das nicht so ohne weiteres; man müsste etwa shared memory oder pipes verwenden, um mit dem neuen Prozess zu kommunizieren. Threads können (im Gegensatz zu Prozessen) auch auf globale Variablen des Programms, auf geöffnete Dateien usw. zugreifen. Wenn allerdings ein Thread abstürzt, ist auch das Programm hin, das ihn gestartet hat. Im Prinzip werden Threads immer nach demselben Muster gestartet: Man definiert eine Funktion, die in einem eigenen Thread ablaufen soll und verwendet eine Funktion des Betriebssystems, um die Thread-Funktion in einem eigenen Thread zu starten. Bei Beendigung der Thread-Funktion wird automatisch auch der Thread beendet.
Leider sind Threads nicht Bestandteil von Standard-C++. Deshalb gibt es keine standardisierten (portablen) Mechanismen, Threads zu starten. Das folgende Beispiel ist für Windows. Ich habe es unter NT mit den Compilern GCC (Version MinGW 2.95.2-1) und Visual C++ (Version 6.0) getestet. Unter Visual C++ muss das Programm mit der Option "Multithreaded" gebaut werden, sonst gibt es eine Fehlermeldung, dass _beginthread() nicht bekannt ist. Die Option kann man in den Einstellungen des Projekts auf der Seite "C/C++" setzen. Sie ist dort unter der Kategorie "Code Generation" und dort unter "Laufzeitbibliothek" zu finden. Es muss einer der "Multithreaded"-Einträge ausgewählt werden (DLL oder EXE, mit oder ohne Debug-Infos). _beginthread ist (etwa) folgendermaßen deklariert: unsigned long _beginthread(void (*fn)(void*), unsigned stacksize, void *arg);
fn ist der Pointer auf die Funktion, die als eigener Thread ausgeführt werden soll. Sie muss GENAU diese Signatur haben: void (void *);
stacksize gibt an, wieviel Stack das System beim Start des Threads bereitstellen soll. Falls das nicht reicht, kann aber auch mehr Stack beschafft werden. Übergibt man 0, wird ein Standardwert verwendet, der in der Regel ganz in Ordnung ist.
arg ist ein Argument, das an die Threadfunktion übergeben wird. So kriegt man also Daten in den neuen Thread.
Die Rückgabe der Funktion ist -1, falls etwas schief gegangen ist. Ansonsten ist es das Handle des neu gestarteten Threads. Das Handle wird bei Beendigung des Threads vom System wieder freigegeben, man sollte also nicht CloseHandle() aufrufen! (Aber es schadet auch nichts.) Ich habe in den Beispielen auf eine Fehlerbehandlung verzichtet. Um einen Thread vorzeitig zu beenden, sollte man _endthread() aufrufen, damit aufgeräumt wird. Beim normalen Verlassen der Thread-Funktion ist das allerdings nicht nötig, da das dann vom System automatisch gemacht wird. Die erste Version verwendet eine globale Funktion als Thread-Funktion: --------- threads1.cpp ----------------------
C++: |
#include <iostream>
extern "C" { #include <process.h> #include <windows.h> }
// Thread-Funktion. arg ist der Wert, der an _beginthread() als dritter // Parameter übergeben wird. Also entweder 1 oder 2. void threadFn(void *arg) { for(int i = 0; i < 20; i++) { std::cout << reinterpret_cast<unsigned long>(arg) << std::flush; Sleep(100); // 100 ms schlafen (Windows-Funktion). } }
// main int main() { // Zwei Threads starten...... _beginthread(threadFn, 0, reinterpret_cast<void *>(1));
// "threadFn" OHNE Klammern!!! _beginthread(threadFn, 0, reinterpret_cast<void *>(2)); // ... dann selbst etwas ausgeben for(int i = 0; i < 20; i++) { std::cout << 3 << std::flush; Sleep(100); // 100 ms schlafen } // Schließen des Konsole-Fensters erst nach RETURN: std::cout << std::endl << "RETURN druecken..." << std::endl; char buffer[31]; std::cin.getline(buffer, 31); return 0; }
|
Das zweite Beispiel ist ein wenig aufwendiger. Hier wird eine Klasse ThreadTest definiert, die zur Verwaltung eines Threads dient. Als Thread-Funktion wird eine static-Methode der Klasse verwendet. Die Methode muss static sein, da "normale" Methoden als unsichtbaren Parameter den this-Pointer (das Objekt, zu dem sie gehören) erwarten und _beginthread() kommt damit nicht klar. --------- threads1.cpp ----------------------
C++: |
#include <iostream>
extern "C" { #include <process.h> #include <windows.h> }
// Klasse ThreadTest deklarieren. class ThreadTest { public: // Methode zum Starten des Threads. void startThread(char start); private: // Thread-Funktion. Wird an _beginthread() übergeben. static void threadFn(void *arg); // Instanzvariable char _ch; }; // Definition der Methode zum Starten des Threads. // Die Methode übergibt this als dritten Parameter an _beginthread(). // Dieser Parameter taucht dann als arg wieder in threadFn() auf. So hat // die Thread-Funktion Zugriff auf das Objekt von ThreadTest, für das der // Thread gestartet wurde. void ThreadTest::startThread(char start) { _ch = start; _beginthread(threadFn, 0, this); // "threadFn" OHNE Klammern!!! }
// Thread-Funktion. // arg ist das this des Objekts von ThreadTest, das diesen Thread gestartet hat. void ThreadTest::threadFn(void *arg) { ThreadTest *obj = static_cast(arg); for(int i = 0; i < 20; i++) { std::cout << obj->_ch++ << std::flush; Sleep(100); // 100 ms schlafen (Windows-Funktion) } }
int main() { // Zwei Thread-Objekte anlegen und die Threads starten..... ThreadTest tt1; tt1.startThread('a'); ThreadTest tt2; tt2.startThread('A'); // ... und jetzt selbst ein bischen was ausgeben char ch = ' '; for(int i = 0; i < 20; i++) { std::cout << ++ch << std::flush; Sleep(100); // 100 ms schlafen } // Konsole-Fenster erst nach RETURN schließen std::cout << std::endl << "RETURN druecken..." << std::endl; char buffer[31]; std::cin.getline(buffer, 31); return 0; }
|
-- Gruß void* Dieser Post wurde am 06.08.2003 um 10:47 Uhr von Uwe editiert. |