10.8 Threads abbrechen (canceln)
Wird ein Thread abgebrochen bzw. beendet, wurde bisher auch der komplette Thread beendet. Doch auch hierbei ist es möglich, auf eine Abbruchaufforderung zu reagieren. Hierzu sind drei Möglichkeiten vorhanden:
|
PTHREAD_CANCEL_DISABLE – Damit legen Sie fest, dass ein Thread nicht abbrechbar ist. Dennoch bleiben Abbruchaufforderungen von anderen Threads nicht unbeachtet. Diese bleiben bestehen, und es kann ggf. darauf reagiert werden, wenn man den Thread wieder in einen abbrechbaren Zustand mittels PTHREAD_CANCEL_ENABLE setzt. |
|
PTHREAD_CANCEL_DEFERRED – Diese Abbruchmöglichkeit ist die Standardeinstellung bei den Threads. Bei einem Abbruch fährt der Thread so lange fort, bis der nächste Abbruchpunkt erreicht wurde. Man spricht von einem »verzögerten« Abbruchpunkt. Einen solchen »Abbruchpunkt« stellten u. a. Funktionen wie pthread_cond_wait(), pthread_cond_timewait(), pthread_join(), pthread_testcancel(), sem_wait(), sigwait(), open(), close(), read(), write() und noch viele weitere mehr da. |
|
PTHREAD_CANCEL_ASYNCHRONOUS – Hiermit wird der Thread gleich nach dem Eintreffen einer Abbruchaufforderung beendet. Hierbei handelt es sich um einen asynchronen Abbruch. |
Hierzu die Funktionen, womit Sie einem anderen Thread einen Abbruch senden können und wie Sie die Abbruchmöglichkeiten selbst festlegen.
#include <pthread. h.>
int pthread_cancel( pthread_t thread );
int pthread_setcancelstate( int state, int *oldstate );
int pthread_setcanceltype( int type, int *oldtype );
void pthread_testcancel( void );
Mit der Funktion pthread_cancel() schicken Sie dem Thread mit der ID thread einen Abbruchaufforderung. Ob der Thread gleich abbricht oder erst beim nächsten Abbruchpunkt, hängt davon ab, ob hier PTHREAD_CANCEL_DEFERRED (Standard) oder PTHREAD_CANCEL_ASYNCHRONOUS verwendet wird. Bevor sich der Thread beendet, werden noch, falls verwendet, alle Exit-Handler-Funktionen ausgeführt.
Mit der Funktion pthread_setcancelstate() legen Sie fest, ob der Thread auf eine Abbruchaufforderung reagieren soll (PTHREAD_CANCEL_ENABLE = Default) oder nicht (PTHREAD_CANCEL_DISABLE). Im zweiten Parameter oldstate können Sie den zuvor eingestellten Wert für den Thread in der übergebenen Adresse sichern – oder, falls nicht benötigt, NULL angeben.
Die Funktion pthread_setcanceltype() hingegen legt über den Parameter type fest, ob der Thread verzögert (PTHREAD_CANCEL_DEFERRED = Default) oder asynchron (PTHREAD_CANCEL_ASYNCHRONOUS) beendet werden soll. Auch hier können Sie den alten Zustand des Threads in der Adresse oldtype sichern oder NULL verwenden.
Mit der Funktion pthread_testcancel() können Sie überprüfen, ob eine Abbruchaufforderung anliegt. Lag eine Abbruchbedingung vor, dann wird der Thread tatsächlich auch beendet. Sie können damit praktisch auch einen eigenen Abbruchpunkt festlegen.
Hier zunächst ein einfaches Beispiel zu pthread_cancel().
/* thread14.c */
#include <stdio.h>
#include <pthread. h.>
#include <stdlib.h>
#include <time.h>
pthread_t t1, t2, t3;
static int zufallszahl;
static void cancel_test1 (void) {
if (zufallszahl > 25) {
pthread_cancel (t3);
printf ("(%d): Thread %ld beendet %ld\n",
zufallszahl, pthread_self(), t3);
}
printf ("%ld zuende\n", pthread_self());
pthread_exit ((void *) 0);
}
static void cancel_test2 (void) {
if (zufallszahl <= 25) {
pthread_cancel (t2);
printf ("(%d): Thread %ld beendet %ld\n",
zufallszahl, pthread_self(), t2);
}
printf ("%ld zuende\n", pthread_self());
pthread_exit ((void *) 0);
}
static void zufall (void) {
srand (time (NULL));
zufallszahl = rand () % 50;
pthread_exit (NULL);
}
int main (void) {
if ((pthread_create (&t1, NULL, zufall, NULL)) != 0) {
fprintf (stderr, "Fehler bei pthread_create ...\n");
exit (EXIT_FAILURE);
}
if((pthread_create(&t2, NULL, cancel_test1, NULL))!=0) {
fprintf (stderr, "Fehler bei pthread_create ...\n");
exit (EXIT_FAILURE);
}
if((pthread_create(&t3, NULL, cancel_test2, NULL))!=0) {
fprintf (stderr, "Fehler bei pthread_create ...\n");
exit (EXIT_FAILURE);
}
pthread_join (t1, NULL);
pthread_join (t2, NULL);
pthread_join (t3, NULL);
return EXIT_SUCCESS;
}
Hier werden drei Threads erzeugt. Einer der Threads erzeugt eine Zufallszahl, die anderen zwei Threads reagieren entsprechend auf diese Zufallszahl. Je nachdem, ob die Zufallszahl kleiner bzw. größer als 25 ist, beendet der eine Thread den anderen mit pthread_cancel(). Wenn Sie das Programm ausführen, wird trotzdem, nach Beendigung einer der beiden Threads mit pthread_cancel(), zweimal ausgegeben:
Thread n beendet
Wie kann das sein, wo Sie doch mindestens einen Thread beendet haben? Das ist die zweite Bedingung zur Beendigung von Threads, nämlich die Reaktion auf die Abbruchanforderungen. Die Standardeinstellung lautet hier ja PTHREAD_CANCEL_DEFERRED. Damit läuft der Thread noch bis zum nächsten Abbruchpunkt, in unserem Fall pthread_exit(). Wenn Sie einen Thread sofort abbrechen wollen bzw. müssen, müssen Sie mit pthread_setcanceltype() die Konstante PTHREAD_CANCEL_ASYNCHRONOUS setzen, z. B. in der main-Funktion mit:
if ((pthread_setcanceltype( PTHREAD_CANCEL_ASYNCHRONOUS,
NULL))!= 0) {
fprintf(stderr, "Fehler bei pthread_setcanceltype\n");
exit (EXIT_FAILURE);
}
In der Praxis kann man aber von asynchronen Abbrüchen abraten, da diese an jeder Stelle auftreten können. Wird z. B. pthread_mutex_lock() aufgerufen und tritt hier der Abbruch ein, nachdem das Mutex gesperrt wurde, hat man schnell einen Deadlock erzeugt. Einen asynchronen Abbruch sollte man in der Praxis nur verwenden, wenn die Funktion »asynchronsicher« ist, was mit pthread_cancel(), pthread_setcancelstate() und pthread_setcanceltype() nicht allzu viele Funktionen sind. Wenn Sie schon asynchrone Abbrüche verwenden müssen, dann eben immer wenn ein Thread keine wichtigen Ressourcen beinhaltet (wie reservierter Speicherplatz (Memory Leaks), Sperren etc.).
Ein besonders häufiger Anwendungsfall von PTHREAD_CANCEL_DISABLE sind kritische Codebereiche, die auf keinen Fall abgebrochen werden dürfen. Z. B. ist dies sinnvoll bei wichtigen Einträgen in Datenbanken, bei komplexen Maschinensteuerungen. Am besten realisiert man solche Codebereiche, indem man den kritischen Abschnitt als unabbrechbar einrichtet und gleich danach den alten Zustand wiederherstellt.
int oldstate;
/* Thread als unabbrechbar einrichten */
if ((pthread_setcancelstate( PTHREAD_CANCEL_DISABLE,
&oldstate))!= 0) {
fprintf(stderr, "Fehler bei pthread_setcancelstate\n");
exit (EXIT_FAILURE);
}
/* ----------------------------------------- */
/* Hier kommt der kritische Codebereich rein */
/* ----------------------------------------- */
/* Alten Zustand des Threads wieder herstellen */
if ((pthread_setcancelstate(oldstat, NULL))!= 0) {
fprintf(stderr, "Fehler bei pthread_setcancelstate\n");
exit (EXIT_FAILURE);
}
Ein einfaches Beispiel hierzu:
/* thread15.c */
#include <stdio.h>
#include <pthread. h.>
#include <stdlib.h>
#include <time.h>
static void cancel_test (void) {
int oldstate;
/* Thread als unabbrechbar einrichten */
if ((pthread_setcancelstate( PTHREAD_CANCEL_DISABLE,
&oldstate))!= 0) {
printf("Fehler bei pthread_setcancelstate\n");
exit (EXIT_FAILURE);
}
printf("Thread %ld im kritischen Codeabschnitt\n",
pthread_self());
sleep(5); // 5 Sekunden warten
/* Alten Zustand des Threads wieder herstellen */
if ((pthread_setcancelstate(oldstate, NULL))!= 0) {
printf("Fehler bei pthread_setcancelstate\n");
exit (EXIT_FAILURE);
}
printf("Thread %ld nach dem kritischen Codeabschnitt\n",
pthread_self());
pthread_exit ((void *) 0);
}
int main (void) {
pthread_t t1;
int *abbruch;
printf("Main-Thread %ld gestartet\n", pthread_self());
if((pthread_create(&t1, NULL, cancel_test, NULL)) != 0) {
fprintf (stderr, "Fehler bei pthread_create ...\n");
exit (EXIT_FAILURE);
}
/* Abbruchaufforderung an den Thread */
pthread_cancel(t1);
pthread_join (t1, &abbruch);
if( abbruch == PTHREAD_CANCELED )
printf("Thread %ld wurde abgebrochen\n", t1);
printf("Main-Thread %ld beendet\n", pthread_self());
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o thread15 thread15.c -lpthread
$ ./thread15
Main-Thread -1209416608 gestartet
Thread -1209418832 im kritischen Codeabschnitt
Thread -1209418832 nach dem kritischen Codeabschnitt
Thread -1209418832 wird abgebrochen
Main-Thread -1209416608 beendet
Ohne das Setzen von PTHREAD_CANCEL_DISABLE am Anfang des Threads »cancel_test« würde das Beispiel keine fünf Sekunden mehr warten und auch nicht mehr ausgeben »Thread –1209418832 nach dem kritischen Codeabschnitt« – am besten testen Sie dies, indem Sie das Verändern des Cancel-Status auskommentieren oder eben anstatt PTHREAD_CANCEL_DISABLE die Konstante PTHREAD_CANCEL_ENABLE verwenden.
|