![]() |
|
|
Exit-Handler für Threads einrichtenWenn Sie einen Thread beenden, können Sie auch einen Exit-Handler einrichten. Dies wird in der Praxis recht gerne verwendet, um z. B. temporäre Dateien zu löschen, Mutexe freizugeben oder eben sonstige »Reinigungsarbeiten« zu machen. Ein solcher eingerichteter Exit-Handler wird dann automatisch bei Beendigung eines Threads mit z. B. pthread_exit() oder return automatisch ausgeführt. Das Prinzip ist ähnlich, wie Sie es von der Standardbibliotheksfunktion atexit() kennen sollten. Auch hierbei werden bei mehreren Exit-Handlern die einzelnen Funktionen in umgekehrter Reihenfolge (da Stack) der Einrichtung ausgeführt. Hier die Funktionen dazu: #include <pthread. h.> void pthread_cleanup_push( void (*function)(void *), void *arg ); void pthread_cleanup_pop( int exec ); Eine solche Funktion richten Sie also mit der Funktion pthread_cleanup_push() ein. Als ersten Parameter übergeben Sie dabei die Funktion, die ausführt werden soll, und als zweiten Parameter die Argumente für den Exit-Handler (falls nötig). Den zuletzt eingerichteten Exit-Handler können Sie wieder mit der Funktion pthread_cleanup_pop() vom Stack entfernen. Geben Sie allerdings einen Wert ungleich 0 als Parameter exec an, so wird diese Funktion zuvor noch ausgeführt, was bei einer Angabe von 0 nicht gemacht wird. Etwas, was mich hier schon zur Weißglut gebracht hat, ist, dass die beiden Funktionen pthread_cleanup_push() und pthread_cleanup_pop() als Makros implementiert sind. Was nicht so schlimm wäre, wenn pthread_cleanup_push() eine sich öffnend geschweifte Klammer enthält und pthread_cleanup_pop() eine sich schließende. Dies bedeutet, Sie müssen beide Funktionen im selben Anweisungsblock ausführen. Daher müssen Sie immer ein _push und ein _pop verwenden, auch wenn Sie wissen, dass eine _pop-Stelle nie erreicht wird. 10.5.3 pthread_join – auf das Ende eines Threads warten
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Hinweis am Rande Bevor sich jemand über die Warnmeldung des Compilers wundert, noch ein Satz zum Casten von void *. In der Programmiersprache C ist ein Casten von oder nach void * nicht nötig. Aber wen die Warnmeldung stört, der kann dies gerne trotzdem nachholen. |
Dieses Beispiel demonstriert auch auf einfache Weise, wie Sie Daten an einen neu erzeugten Thread übergeben können (hier mit der Struktur data). Ebenfalls gezeigt wurde hier die Verwendung eines Exit-Handlers, der nur den im Haupt-Thread angeforderten Speicherbereich freigibt. Zugegeben, das ließe sich auch im Thread »mythread« einfacher realisieren, aber zu Anschauungszwecken sind solch einfache Codebeispiele immer noch am besten. Im Beispiel wurden außerdem drei Threads »mythread« erzeugt, die im Prinzip alle dasselbe machen, nämlich eine einfache Ausgabe der Daten, die an die Threads übergeben wurden. Hierbei muss nochmals explizit darauf hingewiesen werden, dass die Ausführung, in welcher Reihenfolge die Threads starten, nicht vorgegeben ist, auch wenn dies hier einen anderen Anschein macht. Hierzu werden Synchronisationsmechanismen erforderlich. Jeder Thread wurde hier mit pthread_exit() und der eignen Thread-ID als Rückgabewert beendet. Genauso gut kann dies natürlich auch mit return gemacht werden. Der Rückgabewert von den einzelnen Threads wird im Haupt-Thread von pthread_join() erwartet und ausgegeben. Der Haupt-Thread beendet sich am Ende erst, wenn alle Threads fertig sind.
Würden Sie in diesem Beispiel pthread_join() weglassen, so würde sich der Haupt-Thread noch vor den anderen Threads beenden. Dies bedeutet, dass alle anderen Threads zwar noch laufen, aber auf nun nicht mehr gültige Strukturvariablen zugreifen würden.
Zwar wurde schon auf den Rückgabewert von Threads eingegangen, aber hierbei wurden nur Thread-spezifische Daten zurückgegeben (hier die Thread-ID). Aber genauso wie schon bei der Wertübergabe an Threads können Sie hierbei auch ganze Strukturen zurückgeben, was in der Praxis auch häufig so der Fall ist. Hierzu ein ähnliches Beispiel wie schon »thread1.c«, nur dass jetzt die Daten der Struktur aus dem Thread zurückgegeben und im Haupt-Thread mit pthread_join() »abgefangen« und anschließend ausgegeben werden. Auf die Verwendung eines Exit-Handlers wurde der Übersichtlichkeit halber zuliebe verzichtet.
/* thread2.c */
#include <stdio.h>
#include <stdlib.h>
#include <pthread. h.>
/* insg. MAX_THREADS Threads erzeugen */
#define MAX_THREADS 3
#define BUF 255
/* Einfache Daten für die Wertübergabe an den Thread */
struct data {
int wert;
char msg[BUF];
};
/* Die Thread-Funktion */
static void *mythread (void *arg) {
struct data *f= (struct data *)arg;
/* Zufallszahl zwischen 1 und 10 (Spezial) */
f->wert = 1+(int) (10.0*rand()/(RAND_MAX+1.0));
snprintf (f->msg, BUF, "Ich bin Thread Nr. %ld",
pthread_self());
/* Thread beenden - Als Rückgabewert Strukturdaten
* verwenden - Alternativ auch pthread_exit( f ); */
return arg;
}
int main (void) {
pthread_t th[MAX_THREADS];
int i;
struct data *ret[MAX_THREADS];
/* Haupt-Thread gestartet */
printf("\n-> Main-Thread gestartet (ID:%ld)\n",
pthread_self());
/* Speicher reservieren */
for (i = 0; i < MAX_THREADS; i++){
ret[i] = (struct data *)malloc(sizeof(struct data));
if(ret[i] == NULL) {
printf("Konnte keinen Speicher reservieren ...!\n");
exit(EXIT_FAILURE);
}
}
/* MAX_THREADS erzeugen */
for (i = 0; i < MAX_THREADS; i++) {
/* Jetzt Thread erzeugen */
if(pthread_create(&th[i],NULL,&mythread,ret[i]) !=0) {
fprintf (stderr, "Konnte Thread nicht erzeugen\n");
exit (EXIT_FAILURE);
}
}
/* Auf das Ende der Threads warten */
for( i=0; i < MAX_THREADS; i++)
pthread_join(th[i], (void **)&ret[i]);
/* Daten ausgeben */
for( i=0; i < MAX_THREADS; i++) {
printf("Main-Thread: Daten empfangen: \n");
printf("\t\twert = \"%d\"\n", ret[i]->wert);
printf("\t\tmsg = \"%s\"\n", ret[i]->msg);
}
/* Haupt-Thread ist jetzt auch fertig */
printf("<- Main-Thread beendet (ID:%ld)\n",
pthread_self());
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o thread2 thread2.c -lpthread $ ./thread2 -> Main-Thread gestartet (ID:-1209412512) Main-Thread: Daten empfangen: wert = "9" msg = "Ich bin Thread Nr. -1209414736" Main-Thread: Daten empfangen: wert = "4" msg = "Ich bin Thread Nr. -1217807440" Main-Thread: Daten empfangen: wert = "8" msg = "Ich bin Thread Nr. -1226200144" <- Main-Thread beendet (ID:-1209412512)
Um einen Thread mit einem anderen Thread zu vergleichen, kann die Funktion pthread_equal() verwendet werden. Dies wird häufig verwendet, um sicherzugehen, dass nicht ein Thread gleich derselbe ist. Ein Wert ungleich 0 wird zurückgegeben, wenn beide Threads gleich sind, und 0 wird zurückgegeben, wenn die Threads eine unterschiedliche Identifikationsnummer (ID) besitzen.
Das folgende Beispiel erzeugt drei Threads mit derselben »Funktion«, hierbei soll jeder Thread wiederum eine andere Aktion ausführen. Im Beispiel ist dies zwar nur die Ausgabe eines Textes, aber in der Praxis könnten Sie hierbei neue Funktionen aufrufen. Für die ersten drei Threads wird jeweils eine bestimmte Aktion festgelegt. Alle anderen Threads führen nur noch die else-Aktion aus. Dies ist z. B. sinnvoll, wenn Sie in Ihrer Anwendung Vorbereitungen treffen wollen (im Beispiel eben drei Vorbereitungen) so wie Dateien anlegen, Müll beseitigen, eine Server-Verbindung herstellen und noch vieles mehr. Sind diese Vorbereitungen getroffen, wird immer mit der gleichen Funktion fortgefahren. Damit der Vergleich von Threads mit pthread_equal() auch funktioniert, wurden die Thread-IDs, die beim Anlegen mit pthread_create() erzeugt worden sind, in globale Variablen gespeichert – und sind daher auch für alle Threads »sichtbar«. Hier das Beispiel, dessen Ausgabe eigentlich auch einiges erklärt.
/* thread3.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd. h.>
#include <pthread. h.>
#define MAX_THREADS 5
#define BUF 255
/* Globale Variable mit Thread-IDs *
* für alle Threads sichtbar */
static pthread_t th[MAX_THREADS];
static void aktion(void *name) {
while( 1 ) {
if(pthread_equal(pthread_self(),th[0])) {
printf("\t->(%ld): Aufgabe \"abc\" Ausführen \n",
pthread_self());
break;
}
else if(pthread_equal(pthread_self(),th[1])) {
printf("\t->(%ld): Aufgabe \"efg\" Ausführen \n",
pthread_self());
break;
}
else if(pthread_equal(pthread_self(),th[2])) {
printf("\t->(%ld): Aufgabe \"jkl\" Ausführen \n",
pthread_self());
break;
}
else {
printf("\t->(%ld): Aufgabe \"xyz\" Ausführen \n",
pthread_self());
break;
}
}
pthread_exit((void *)pthread_self());
}
int main (void) {
int i;
static int ret[MAX_THREADS];
printf("->Haupt-Thread (ID:%ld) gestartet...\n",
pthread_self());
/* Threads erzeugen */
for (i = 0; i < MAX_THREADS; i++) {
if (pthread_create (&th[i],NULL,&aktion,NULL) != 0) {
printf ("Konnte keinen Thread erzeugen\n");
exit (EXIT_FAILURE);
}
}
/* Auf die Threads warten */
for (i = 0; i < MAX_THREADS; i++)
pthread_join (th[i], &ret[i]);
/* Rückgabe der Threads auswerten */
for (i = 0; i < MAX_THREADS; i++)
printf("\t<-Thread %ld mit Arbeit fertig\n", ret[i]);
printf("->Haupt-Thread (ID:%ld) fertig ...\n",
pthread_self());
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o thread3 thread3.c -lpthread $ ./thread3 ->Haupt-Thread (ID:-1209412512) gestartet... ->(-1209414736): Aufgabe "abc" Ausführen ->(-1217807440): Aufgabe "efg" Ausführen ->(-1226200144): Aufgabe "jkl" Ausführen ->(-1234592848): Aufgabe "xyz" Ausführen ->(-1242985552): Aufgabe "xyz" Ausführen <-Thread -1209414736 mit Arbeit fertig <-Thread -1217807440 mit Arbeit fertig <-Thread -1226200144 mit Arbeit fertig <-Thread -1234592848 mit Arbeit fertig <-Thread -1242985552 mit Arbeit fertig ->Haupt-Thread (ID:-1209412512) fertig ...
Sie können daran erkennen, dass die ersten drei Threads jeweils »abc«, »efg« und »jkl« ausführen. Alle noch folgenden Threads führen dann »xyz« aus. Zugegeben, das lässt sich eleganter mit den Synchronisationsmechanismen der Thread-Bibliothek lösen, aber das Beispiel demonstriert den Sachverhalt der Funktion pthread_equal() recht gut.
Das Gegenteil von pthread_join() stellt die Funktion pthread_detach() dar. Mit dieser Funktion legen Sie fest, dass nicht mehr auf die Beendigung des Threads gewartet werden soll.
#include <pthread. h.> int pthread_detach( pthread_t thread );
Sie lösen hiermit praktisch den Thread mit der ID thread von der Hauptanwendung los. Sie können diesen Vorgang gerne mit den Daemon-Prozessen vergleichen. Dass dieser Thread dann selbstständig ist, ist nichts Magisches, im Grunde »markieren« Sie den Thread damit nur, so dass bei seinem Beenden der Exit-Status und die Thread-ID gleich freigegeben werden. Ohne pthread_detach() würde dies erst der Fall nach einem pthread_join-Aufruf sein. Natürlich bedeutet die Verwendung von pthread_detach(), dass hierbei auch kein pthread_join() mehr auf das Ende des Threads reagiert.
|
Hinweis Ein Thread, der mit pthread_detach() oder dem Attribut PTHREAD_CREATE_DETACHED von den anderen Threads losgelöst wurde, kann nicht mehr mit pthread_join() abgefangen werden. Der Thread läuft praktisch ohne äußere Kontrolle weiter. |
Ein typischer Codeausschnitt, wie Sie einen Thread von den anderen loslösen können, sieht wie folgt aus:
pthread_t a_thread;
int ret;
...
/* Einen neuen Thread erzeugen */
ret = pthread_create( &a_thread, NULL,
thread_function, NULL);
/* bei Erfolg den Thread abhängen ... */
if (ret == 0) {
pthread_detach(a_thread);
}
| << zurück |
|
||||||||||||
|
||||||||||||
|
||||||||||||
Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich
ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen,
wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist
urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der
Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und
Verarbeitung in elektronischen Systemen.