Homepage    Computer-Stoff(Titelseite)   Threads(Titelseite)

Canceln von Threads

Manchmal möchte man, daß ein Thread einem anderen Thread mitteilt, daß dieser sich beenden soll - sei es sofort, sei es an vorgegebenen Haltepunkten. Einen solchen Vorgang nennt man "einen Thread canceln". Wie man dies macht, ist Thema dieser Datei.


Cancel-Requests

Einem anderen Thread mitzuteilen, daß er sich beenden soll, ist ganz einfach. Es geht mit der Funktion

int pthread_cancel (pthread_t th);

In der Variablen th wird der Thread übergeben, der sich beenden soll. Mit dem Absenden dieses Cancel-Requests ist allerdings nicht garantiert, daß sich der andere Thread auch tatsächlich beendet. Dies hängt von seinem sog. "Cancel-Status" und seinem "Cancel-Typ" ab.

Falls sich jedoch ein Thread aufgrund eines Cancel-Requests selbst beendet, gibt er den Wert PTHREAD_CANCELED zurück. Wird ein Thread gecancelt, so ist es also, als ob er sich mit

   pthread_exit (PTHREAD_CANCELED);

beendet.


Cancel-Status und Cancel-Typ

Jeder Thread hat einen sog. "Cancel-Status" (cancelstate) und einen "Cancel-Typ" (canceltype).

Der Cancel-Status kann folgende Werte haben:

PTHREAD_CANCEL_ENABLE Der Thread reagiert auf Cancel-Requests.
PTHREAD_CANCEL_DISABLE Der Thread ignoriert sämtliche Cancel-Requests. Diese werden aber nicht vergessen, sondern bleiben über ihm wie in Damokles-Schwert hängen. Sobald der Thread seinen Cancel-Status wieder enabled, führt er sie auch aus.

Der Cancel-Typ kann folgende Werte haben:

PTHREAD_CANCEL_ASYNCHRONOUS Falls der Thread einen Cancel-Request empfängt, reagiert er unverzüglich und beendet sich.
PTHREAD_CANCEL_DEFERRED Falls der Thread einen Cancel-Request empfängt, beendet er sich nicht sofort, sondern nur an bestimmten Punkten im Programm, den sog. "Cancel-Punkten".

Der Cancel-Typ spielt natürlich nur dann eine Rolle, wenn der Cancel-Status enabled ist.

Seinen Cancel-Status und seinen Cancel-Typ kann ein Thread mit den selbsterklärenden Funktionen

int pthread_setcancelstate (int state, int *oldstate);
int pthread_setcanceltype  (int type,  int *oldtype);

setzen. Die Argumente state und type müssen dabei einen der obigen Werte haben. Die beiden übergebenen Zeiger dürfen auch NULL sein.


Cancel-Punkte

Cancel-Punkte sind Befehle, an denen ein Thread, dessen Cancel-Status PTHREAD_CANCEL_ENABLE und dessen Cancel-Typ PTHREAD_CANCEL_DEFERRED ist, testet, ob er gecancelt worden ist. Welche Befehle das genau sind (es sind nicht viele) kann man auf der man-page von pthread_cancel nachlesen. Wichtig hier ist nur der Befehl

void pthread_testcancel (void);

Dieser macht nichts anderes, als eben zu testen, ob es einen Cancel-Request für den Thread gegeben hat.


Eine primitives Beispiel

/* beispiel3.c */
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int i=0;

void *count_up (void *dummy)
{
  pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL);
  pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

  while (1) {
    printf ("%i\n", i);
    if (i<100) i++;
  }
  return NULL;
}


int main ()
{
  pthread_t p;
  int oldval=-1;

  pthread_create (&p, NULL, count_up, NULL);

  while (oldval != i) {
    oldval = i;
    sleep(1);
  }

  pthread_cancel (p);
  pthread_join (p, NULL);
  return 0;
}

In diesem Programm läuft der neu erzeugte Thread in einer Endlosschleife. Er zählt die globale Variable i bis zu einer gewissen Zahl (hier 100) hoch, und stellt dann seine Arbeit ein.

Der Hauptthread testet jede Sekunde, ob sich der Wert von i in der letzten Sekunde noch geändert hat. (Also gewissermaßen, ob der Thread noch arbeitet.) Ist dies nicht mehr der Fall, so schickt er einen Cancel-Request an den Thread, dem dieser auch sofort gehorcht.

Die Ausgabe dieses Programmes besteht also zunächst einmal aus den Zahlen von 0 bis 100. Schließlich kommen nur noch 100er, solange, bis der Hauptthread bemerkt hat, daß sich daran auch nichts mehr ändert.

Würde man den pthread_cancel-Befehl auskommentieren, so würde sich das Programm nie beenden, denn der Haupt-Thread würde beim pthread_join-Befehl ewig warten, weil sich der neue Thread nie beendet.

(Was passieren würde, wenn man auch noch den pthread_join-Befehl auskommentieren würde, sollte der Leser jetzt selber schon wissen.)


Das Canceln von Threads und Mutexe

Da das Canceln eines Threads wie oben erwähnt äquivalent einem pthread_exit(PTHREAD_CANCELED) ist, muß man darauf achten, daß, egal an welcher Stelle der Thread gecancelt wird, er keine Mutexe mehr besetzt hat. Anders ausgedrückt: An Stellen, an denen ein Thread einen Mutex besetzt, darf er nicht gecancelt werden können, oder der Mutex muß in einem Cleanup-Handler wieder freigegeben werden.

Ist andersherum ein Thread durch einen gesetzten Mutex blockiert, so wird er, selbst wenn sein Cancel-Typ PTHREAD_CANCEL_ASYNCHRONOUS ist, solange nicht auf einen Cancel-Request reagieren, bis der Mutex wieder freigegeben wird, und er wieder weiterlaufen kann.


Cleanup-Handler

Ein Problem beim Canceln ist, daß der Thread mitten aus seinem normalen Ablauf gerissen wird. Es sollte dennoch eine Möglichkeit geben, daß der Thread von ihm allokierten Speicher vorher wieder freigibt, oder einen Mutex wieder freigibt. Hierzu gibt es die sog. "Cleanup-Handler". Dies sind Funktionen, die von einem Thread aufgerufen werden, wenn er sich, sei es durch einen Cancel-Request, sei es durch ein pthread_exit beendet.

Und zwar verwaltet jeder laufende Thread einen Stack von Funktionspointern, auf dem man neue Cleanup-Funktionen ablegen kann, oder sie wieder runterholen kann:

void pthread_cleanup_push (void (*routine)(void*), void *arg);
void pthread_cleanup_pop  (int execute);

Bei Beendigung eines Threads werden diese der Reihe nach (in LIFO-Ordnung) mit den angegebenen Argumenten aufgerufen. Damit das klappt, müssen sich entsprechende push- und pop-Befehle in derselben Funktion befinden. Ansonsten macht auch das "arg" keinen Sinn.

(Mit Cleanup-Handlern habe ich auch noch nicht gearbeitet. Deshalb kann ich nicht viel mehr darüber sagen.)


Homepage   Computer-Stoff(Titelseite)   Threads(Titelseite) by Michael Becker, 6/2001 Letzte Änderung: 6/2001.