Homepage    Computer-Stoff(Titelseite)   Threads(Titelseite)

Threads starten und beenden


Das erste Beispielprogramm

Im Rest dieses Abschnitts werde ich die grundsätzlichen Funktionen zum Erzeugen und Beenden von Threads an folgendem kleinen Beispielprogramm besprechen:

/* beispiel1.c */
#include <stdio.h>
#include <pthread.h>

void *print_char (void *ch)
{
  int i;
  for (i=0; i<10; i++)
    printf ("%c", *(char*)ch);
  return NULL;
}

int main ()
{
  char ch1='-', ch2='*';
  pthread_t p1, p2;

  pthread_create (&p1, NULL, print_char, &ch1);
  pthread_create (&p2, NULL, print_char, &ch2);

  pthread_join (p1, NULL);
  pthread_join (p2, NULL);

  printf ("\n");
  return 0;
}

In diesem Beispielprogramm erzeugt der "Haupt-Thread", der läuft, wenn man das Programm startet, zwei weitere threads, die beide die Funktion print_char durchlaufen und Minuszeichen bzw. Sterne ausgeben.

Haben sie je 10 solche Zeichen ausgegeben, kehren sie wieder zurück. Der Haupt-Thread wartet auf ihre Rückkehr, gibt noch ein Newline aus, und beendet sich dann.

In unserem Fall ist natürlich der Thread Nr.1 mit seiner Aufgabe schon fertig, bevor der Thread Nr.2 überhaupt angefangen hat. Deshalb wird man hier nicht richtig sehen können, daß beide Threads gleichzeitig arbeiten.


Compilieren von Programmen mit Threads

Dieses Programm beispiel1.c kann beispielsweise compiliert werden mit

   gcc -D_REENTRANT beispiel1.c -lpthread

Im Wesentlichen muß man also die Header-Datei "pthread.h" einbinden, und die entsprechende Library "libpthread" dazulinken.

Das Präprozessor-Makro _REENTRANT sagt dem Compiler, daß thread-sichere Varianten von diversen Funktionen zur Verfügung gestellt werden sollen, die ansonsten in den Header-Dateien mit #ifndef auskommentiert sind. Dies geht insbesondere einige Funktionen aus netdb.h an. Außerdem werden einige Funktionen und Makros umdefiniert (errno, getc). Die meisten Programme funktionieren zwar auch ohne dieses Flag, man sollte sich aber nicht darauf verlassen.

Als "reentrant" bezeichnet man Funktionen, die, während sie gerade am laufen sind, nochmal aufgerufen werden können. Dies kann durch einen zweiten Thread getan werden, aber auch z.B., wenn das Programm in dieser Funktion ein Signal fängt, und der Signal-Handler abermals diese Funktion aufruft.

Die thread-sicheren Varianten haben denselben Namen, enden aber mit einem _r, und nehmen als zusätzliche Argument meist einen Buffer und/oder eine Buffer-Länge. Die meisten dieser Varianten haben keine eigene man-page. Ihre Syntax kann man aber direkt in den betroffenen Header-Dateien nachlesen. Nicht alle der thread-sicheren Funktions-Varianten werden nur nach einem _REENTRANT zur Verfügung gestellt

Diese Makro sorgt außerdem d


Threads starten

Eine neuer thread wird gestartet mit der Funktion

int pthread_create (pthread_t *th,
                    pthread_attr_t *attr,
                    void *(*start_routine)(void*),
                    void *arg);

Im ersten Argument, welches auf einen allokierten Speicherbereich zeigen muß, wird ein Identifier für den neu erzeugten Thread zurückgegeben. Viele Funktionen, die etwas mit einem Thread machen sollen, nehmen ein Argument vom Typ pthread_t, in welchem in diesem Fall dann ein gültiger Identifier eines (noch laufenden) Threads enthalten sein muß.

Das zweite Argument attr kann Attribute, enthalten, die für den neu erzeugten Thread gelten sollen. Hierzu mehr im Abschnitt über Thread-Attribute. Übergibt man NULL, so bekommt der Thread Standard-Eigenschaften.

Das dritte Argument schließlich ist ein Zeiger auf eine Funktion, mit der der neue Thread starten soll. Dieser Funktion wird als Argument arg, das vierte Argument der pthread_create-Funktion übergeben. Will man dem neuen Thread Daten übergeben -wie hier den auszudruckenden char- so kann man das hiermit tun.


Threads beenden

Es gibt zwei Möglichkeiten, wie sich ein Thread normalerweise beendet:

  1. Er trifft ein einen return-Befehl in der Funktion, in der er gestartet wurde. Diese Möglichkeit sieht man im Beispielprogramm.
  2. Durch den Befehl
void pthread_exit (void *retval);

In beiden Fällen hat man die Möglichkeit, einen Wert von Typ void* zurückzugeben.

Es gibt auch noch die Möglichkeit, einen anderen Thread zu canceln, d.h. eine Aufforderung an ihn zu senden, sich auf der Stelle oder bei Gelegenheit zu beenden. Hierzu später mehr. Außerdem hat man die Möglichkeit, dafür zu sorgen, daß jeder Thread, wenn er sich beendet, vorher noch eine Reihe von Funktionen aufruft. Solche Funktionen nennt man dann üblicherweise Cleanup-Handler.


Auf die Beendigung eines Threads warten

In unserem Beispielprogramm ist es wichtig, das der Hauptthread wartet, bis die beiden von ihm gestarteten Threads fertig sind. Ansonsten wird nämlich der Prozeß beendet. Und da alle Threads zu demselben Prozeß gehören, werden sie alle beendet.

int pthread_join (pthread_t th,
                  void **returnval);

Dieser Befehl wartet auf die Beendigung des Threads th. Ist returnval!=NULL, so wird darin der Rückgabewert des beendeten Threads zurückgegeben.


Joinable und detached Threads

Normalerweise bleibt, wenn ein Thread sich beendet hat, sein Identifier weiterhin gültig und er belegt auch Ressourcen im Rechner. Es könnte z.B. sein, daß ein anderer Thread via pthread_join seinen Rückgabewert haben will. Einen solchen Thread nennt man deshalb auch "joinable".

Manchmal ist dies nicht mehr nötig: Der Rückgabewert wird nicht mehr gebraucht und es können alle Ressourcen, die ein Thread belegt, wieder freigegeben werden. Mit Hilfe des Befehls

int pthread_detach (pthread_t th);

teilt man einem Thread mit, daß nach seiner Beendigung sämtliche Ressourcen freigegeben werden können. Er wird dann zu einem "detached thread". Ein solcher Thread läuft dann ohne jegliche äußere Kontrolle bis zum Ende. Es gibt von außerhalb keine Möglichkeit, festzustellen, ob er sich schon beendet hat, oder seinen Rückgabewert zu bekommen. Sein Identifier wird ungültig, wenn er sich beendet.

Bei einem joinable Thread führt der pthread_join-Befehl das Detachen durch.

Threads können auch direkt detached erzeugt werden. (s. im nächsten Abschnitt).


Thread-Attribute

Dem pthread_create-Befehl kann ein Attribut mitgegeben. Die einzigen Befehle, die etwas mit solchen Attributen zu tun haben, die ich hier vorstelle, sind die selbsterklärenden Funktionen

int pthread_attr_init (pthread_attr_t *attr);
int pthread_attr_destroy (pthread_attr_t *attr);
int pthread_attr_getdetachstate (pthread_attr_t *attr, int *detachstate);
int pthread_attr_setdetachstate (pthread_attr_t *attr, int  detachstate);

Das Argument detachstate kann dabei einen der beiden Werte

   PTHREAD_CREATE_JOINABLE
   PTHREAD_CREATE_DETACHED

haben. Es gibt noch andere Attribute, die aber nicht durchgehend portabel sind, und schwerer zu verstehen.


pthread_self und Threads vergleichen

Manchmal ist es für einen Thread nötig, festzustellen, ob er selber zu einem bestimmten Thread-Identifier gehört. Dies kann er mit den beiden folgenden Befehlen machen:

pthread_t pthread_self (void);
int pthread_equal (pthread_t th1, pthread_t th2);

Die erste Funktion liefert einen gültigen Identifier für den aufrufenden Thread zurück. Die zweite Funktion gibt einen Wert !=0 zurück, falls die beiden übergebenen Identifier Identifier für denselben Thread sind. Hier ein kleines Beispielprogramm:

/* beispiel5.c */
#include <stdio.h>
#include <pthread.h>

pthread_t th[2];
char *word[2] = {"this is thread1",
                 "this is thread2"};

void *print_word (void *dummy)
{
  if (pthread_equal (pthread_self(), th[0])) printf ("%s\n", word[0]);
  if (pthread_equal (pthread_self(), th[1])) printf ("%s\n", word[1]);

  return NULL;
}

int main ()
{
  pthread_create (th  , NULL, print_word, NULL);
  pthread_create (th+1, NULL, print_word, NULL);

  pthread_join (th[0], NULL);
  pthread_join (th[1], NULL);

  return 0;
}

In diesem Progrämmchen werden zwei Threads erzeugt, die in derselben Funktion laufen. Dort stellen sie fest, zu welchem der beiden globalen Thread-Identifier gehören und geben dann das zugehörige Wort aus. Aus Gründer der Übersichtlichkeit habe ich hier darauf verzichtet, eine variable Anzahl von Threads zu erzeugen und die Abfrage in einer Schleife zu machen.


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