Sehr Umfangreiche Webseite zum Programmieren in C Perl CGI, Skripting, Linux, Systemprogrammierung C C++ C/C++ ANSI C Linux Linuxsystemprogrammierung Parallele Programmierung Threads Linuxsystemprogrammieren Parallele Programmierung Threads 8. Parallele Programmierung mit Threads

8.4. Threads syncronisieren mit Mutexe           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Wenn Sie mehrere Threads starten und diese parallel ablaufen, können Sie nicht erkennen wie weit welcher Thread gerade mit der Verarbeitung von Daten ist. Wenn mehrer Threads Beispielsweise an ein und der selben Aufgabe abhängig voneinander arbeiten, wird eine Synchronisation erforderlich. Genauso ist dies erforderlich, wenn Threads globale Variablen verwenden, da sonst ein Thread diese Variable einfach überschreiben würde bevor diese Variable noch verwendet würde.

Um Threads zu Synchronisieren haben Sie zwei Möglichkeiten. Zum einen mit sogenannte Locks, die wir in diesem Kapitel mit den Mutexen durchgehen werden und zum anderen einen Monitor. Mit dem Monitor werden sogenannte Condition-Variablen verwendet.

Die Funktionsweise von Mutexen ähnelt den Semphoren bei den Prozessen. Trotzdem lassen sich diese aber wesentlich einfacher erstellen. Das Prinzip ist simpel. Ein Thread arbeitet mit einer globalen oder statischen Variable die für allen anderen Threads von einem Mutex blockiert (gesperrt) wird. Benötigt der Thread diese Variable nicht mehr, gibt er diese frei.

Anhand dieser Erklärung dürfte auch klar sein, dass man selbst wieder dafür Verantwortlich ist, keinen Deadlock zu erzeugen. In folgenden Fällen könnten auch bei Threads Deadlocks auftreten ...

Im Falle eines Deadlocks kann keiner der Beteiligten Threads seine Arbeit mehr fortsetzen, und somit ist meist keine normale Beendigung mehr möglich. Datenverlust kann die Folge sein.

Wir wollen das ganze Anhand eines Bildes Analysieren ...

Mutex

Sie haben hier eine globale Variable i mit dem Wert 1 und drei Funktionen und drei Threads. Jeder Thread greift dabei auf eine Funktion zu. Thread1 soll die Variable multiplizieren. Thread2 addiert die globale Variable. Und am Schluss wird i um den Wert 1 erhöht von Thread3.

Nun geht es in den nächsten Durchgang und i hat nun den Wert 2. Nun wird i von Thread1 multipliziert, Thread2 addiert und wieder von Thread3 um eins erhöht. Dies wollen wir bis 10 machen.

Damit nicht Thread3 den Wert von i um eins erhöht bevor Thread1 oder Thread2 seine Arbeit damit verrichten, werden die einzelnen Threads mit Hilfe eines Mutexes synchronisiert.

Zuvor nun die Funktionen mit denen Sie sogenannte exklusive Sperren einrichten können. Zuerst müssen Sie einen Mutex initialisieren, bevor Sie Ihn verwenden. Dies erledigen Sie mit der Funktion ...

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

Damit Sie den Mutex auch verwenden können für alle Threads, muss dieser logischerweise als globale Variable angegeben werden. Auch Mutexen können Attribute vergeben werden (mutexattr). Wir begnügen uns zu Beginn aber wieder mit dem NULL-Zeiger für den default-Wert als Attribut.

Die Struktur pthread_mutex_t hat folgendes aussehen ...

typedef struct
         {
          int __m_reserved;                   /* Reserved for future use */
          int __m_count;                      /* Depth of recursive locking */
          _pthread_descr __m_owner;           /* Owner thread (if recursive or errcheck)*/
          int __m_kind;                       /* Mutex kind: fast, recursive or errcheck */
          struct _pthread_fastlock __m_lock;  /* Underlying fast lock */
         } pthread_mutex_t;

Nun benötigen Sie noch die Funktionen um einen Mutex zu setzen und Ihn wieder freizugeben ...

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthead_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

Mit den Funktionen pthread_mutex_lock und pthread_mutex_trylock setzen Sie einen Mutex. Der Unterschied der beiden Funktionen ist der, dass pthread_mutex_trylock nicht blockiert, also wartet, bis der Mutex wieder frei ist. pthead_mutex_trylock gibt falls der Mutex besetzt ist EBUSY zurück.

Mit der Funktion pthread_mutex_unlock geben sie den Mutex wieder frei. Geben Sie einen Mutex nicht frei, kann die Folge ein dauerhaftes blockieren sein. Das Programm lässt sich dann meist nur noch mit Gewalt beenden.

Was passiert, wenn Sie einen zweiten Mutex zu setzen versuchen, hängt von den Attributen des Mutexes ab. Dazu später mehr.

Hierzu nun ein Programmbeispiel ...

#include <stdio.h>
#include <pthread.h>

pthread_t th[2];
pthread_mutex_t mutex;

void thread1(void *name)
{
 int i;
 int x;
 for(i=0; i<10; i++)
  {
   printf("Thread : %ld -> locking\n",pthread_self());
 /*Mutex setzen*/
   if(pthread_mutex_lock(&mutex) == 0)
    {
     /*Kritischer Codabschnitt*/
     /*Tu was kritisches*/
     sleep(1);
    }
   else
    {
      printf("locking -> fehlgeschlagen\n");
      exit(0);
    }
 /*Mutex wieder aufheben*/
   if(pthread_mutex_unlock(&mutex) == 0)
     printf("Thread : %ld ->unlock\n",pthread_self());
   else
    {
      printf("Konnte locking von Thread %ld nicht mehr aufheben\n"
              ,pthread_self());
      exit(0);
    }
  }
 pthread_exit((void *) 0);
}


void thread2(void *name)
{
 int i;
 for(i=0; i<10; i++)
  {
   printf("Thread : %ld -> locking\n",pthread_self());
 /*Mutex setzen*/
   if(pthread_mutex_lock(&mutex)==0)
    {
     /*Kritischer Codabschnitt*/
     /*Tu was kritisches*/
     sleep(1);
    }
   else
    {
      printf("locking -> fehlgeschlagen\n");
      exit(0);
    }
 /*Mutex wieder aufheben*/
   if(pthread_mutex_unlock(&mutex) == 0)
     printf("Thread : %ld ->unlock\n",pthread_self());
   else
    {
      printf("Konnte locking von Thread %ld nicht mehr aufheben\n"
              ,pthread_self());
      exit(0);
    }
   }
 pthread_exit((void *) 0);
}

int main()
{
  pthread_t th[2];
  int i;

  /*einen Mutex erzeugen*/
  pthread_mutex_init(&mutex, NULL);

  if(pthread_create(&th[0], NULL, (void *)&thread1, (void *)NULL) != 0)
    {
      fprintf(stderr, "Fehler beim Erzeugen eines Threads.!?!?!\n");
      exit(0);
    }
  if(pthread_create(&th[1], NULL, (void *)&thread2, (void *)NULL) != 0)
    {
      fprintf(stderr, "Fehler beim Erzeugen eines Threads.!?!?!\n");
      exit(0);
    }

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

  return 0;
}

Hier sehen Sie, wie exklusive Sperren gesetzt und wieder freigegeben werden. Damit Sie es aber nicht Missverstehen, wir haben die Threads nicht Synchronisiert. Sie können ja mal die sleep-Funktion herausnehmen. Wir haben hier nur einen kritischen Codebereich vor anderen Threads geschützt.

Jetzt wollen wir uns noch schnell die Lösung unseres Problems von oben ansehen ...

#include <stdio.h>
#include <pthread.h>

pthread_t th[2];
pthread_mutex_t mutex;
int thread_nr=1;
int i=1;

void thread1(void *name)
{
 int a;
 /*Mutex setzen*/
 for(a=0;a<10; a++)
  {
   while(thread_nr != 1);
   if(pthread_mutex_lock(&mutex) == 0)
    {
     printf("%d+%d=%d ",i,i,i+i);
     thread_nr = 2;
    }
   else
    {
      printf("locking -> fehlgeschlagen\n");
      exit(0);
    }
 /*Mutex wieder aufheben*/
   if(pthread_mutex_unlock(&mutex) != 0)
    {
      printf("Konnte locking von Thread %ld nicht mehr aufheben\n"
              ,pthread_self());
      exit(0);
    }
  }
 pthread_exit((void *) 0);
}


void thread2(void *name)
{
 int b;
 /*Mutex setzen*/
 for(b=0;b<10; b++)
  {
   while(thread_nr != 2);
 /*Mutex setzen*/
   if(pthread_mutex_lock(&mutex)==0)
    {
      printf("\t%d*%d=%d\n",i,i,i*i);
      thread_nr=3;
    }
   else
    {
      printf("locking -> fehlgeschlagen\n");
      exit(0);
    }
 /*Mutex wieder aufheben*/
   if(pthread_mutex_unlock(&mutex) != 0)
    {
      printf("Konnte locking von Thread %ld nicht mehr aufheben\n"
              ,pthread_self());
      exit(0);
    }
  }
 pthread_exit((void *) 0);
}


void thread3(void *name)
{
 int c;
 /*Mutex setzen*/
 for(c=0;c<10; c++)
  {
   while(thread_nr != 3);
 /*Mutex setzen*/
   if(pthread_mutex_lock(&mutex)==0)
    {
     i++;
     thread_nr=1;
    }
   else
    {
      printf("locking -> fehlgeschlagen\n");
      exit(0);
    }
 /*Mutex wieder aufheben*/
   if(pthread_mutex_unlock(&mutex) != 0)
    {
      printf("Konnte locking von Thread %ld nicht mehr aufheben\n"
              ,pthread_self());
      exit(0);
    }
  }
 pthread_exit((void *) 0);
}


int main()
{
  pthread_t th[3];
  int i;

  /*einen Mutex erzeugen*/
  pthread_mutex_init(&mutex, NULL);

  if(pthread_create(&th[0], NULL, (void *)&thread1, (void *)NULL) != 0)
    {
      fprintf(stderr, "Fehler beim Erzeugen eines Threads.!?!?!\n");
      exit(0);
    }

  if(pthread_create(&th[1], NULL, (void *)&thread2, (void *)NULL) != 0)
    {
      fprintf(stderr, "Fehler beim Erzeugen eines Threads.!?!?!\n");
      exit(0);
    }
  if(pthread_create(&th[2], NULL, (void *)&thread3, (void *)NULL) != 0)
    {
      fprintf(stderr, "Fehler beim Erzeugen eines Threads.!?!?!\n");
      exit(0);
    }

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

  return 0;
}

Zugegeben, dass lässt sich mit einen einfachen Iterativen Programm erheblich leichter und schneller errechnen. Und ich glaube kaum das Threads für die Grundrechenarten erfunden wurden. Es soll auch nur zeigen, wie man eine bestimmte Variable von den anderen Threads schützt, aber trotzdem alle Threads mit dieser Variable arbeiten können und zwar ohne Probleme.

Um einen Mutex wieder zu löschen, gibt es die Funktion ...

int pthread_mutex_destroy(pthread_mutex_t *mutex);

Mutex Attribute
Es gibt zwei Arten von Mutex-Attributen die Sie verwenden können. Zum einen wären das Attribute bei denen ein Thread versucht, einen von ihm gesetzten Mutex nochmals zu setzen und zum anderen wenn ein Thread versucht den Mutex eines anderen Threads freizugeben.

Abfragen und Setzen können Sie diese Attribute mit den Funktionen ...

int pthread_mutexattr_setkind_np(pthread_mutexattr_t *attr, int kind);
int pthread_mutexattr_getkind_np(pthread_mutexattr_t *attr, int *kind);

Folgende Konstanten können Sie für kind setzen oder abfragen, wenn ein Thread versucht einen Mutex erneut zu setzen ...

Folgende Konstanten können Sie verwenden zum Setzen bzw. Abfragen, wenn ein Thread versucht den gesetzten Thread eines anderen Mutexes freizugeben ...

Um Mutex-Attribute Abfragen oder Setzen zu können, müssen Sie diese zuerst initialisieren mit der Funktion ...

int pthread_mutexattr_init(pthread_mutexattr_t *attr);

Außerdem gilt für die Konstante...

Und hierzu folgt wieder ein Programmbeispiel ...

#include <stdio.h>
#include <pthread.h>
#include <asm/errno.h>

pthread_mutex_t MUTEX;
pthread_mutexattr_t attribut;

void rechne(void)
{
  int i;
  for(i=0;i<4; i++)
    {
      if(pthread_mutex_trylock(&MUTEX) == 0)
        {
          printf("Funktion \"hallo1()\" aufgerufen\n");
          if( (pthread_mutex_unlock(&MUTEX)) !=0)
            {
              fprintf(stderr,"Konnte Sperre nicht mehr aufheben....\n");
              exit(0);
            }
        }
      else if(i==1)
        pthread_mutex_unlock(&MUTEX);
      else
        printf("Funktion noch nicht berreit.....\n");
    }
}


int main()
{
   pthread_t t1;

   if(pthread_mutexattr_init(&attribut) != 0)
    {
      fprintf(stderr, "Kein Mutexerstellung möglich.........\n");
      exit(0);
     }

  if(pthread_mutexattr_setkind_np(&attribut, PTHREAD_MUTEX_RECURSIVE_NP) !=0)
     fprintf(stderr, "Konnte Attribut PTHREAD_MUTEX_RECURSIVE_NP nicht setzen\n");

  if((pthread_mutex_init(&MUTEX, &attribut)) !=0)
    {
      fprintf(stderr, "Fehler bei pthread_mutex_init.......\n");
      exit(0);
    }
  pthread_mutex_lock(&MUTEX);
  if((pthread_create(&t1, NULL, (void *)&rechne, NULL)) !=0)
    {
     fprintf(stderr, "Fehler bei ptread_create.........\n");
     exit(0);
    }

  pthread_join(t1, NULL);
  pthread_mutex_destroy(&MUTEX);
  pthread_mutexattr_destroy(&attribut);

  return 0;
}

Dies Programm ist das selbe wie im Kapitel zuvor, nur mit Beispielen der Funktionen die Sie in diesem Kapitel gelernt haben und mit dem Attribut PTHREAD_MUTEX_RECURSIV_NP. Somit ist im Gegensatz von zuvor, die Funktion nie bereit. In diesem Fall scheint der interne rekursive Zähler beim Funktionsaufruf auf 0 gestellt worden zu sein. Und somit funktioniert auch die Freigabe bei i==1 nicht, da der interner Zähler nicht auf 1 steht. Den für PTHREAD_MUTEX_RECURSIV_NP gilt ja, es können nur so viele Mutexe freigegeben werden, wie gesperrt wurden. Und bei Aufruf einer neuen Funktion scheint dieser interne Zähler ebenso auf 0 gestellt worden zu sein. Wenn Sie die Mutex-Sperre in der main-Funktion auskommentieren wird diese Aussage bestätigt.

8.5. Race conditions mit Condition Variablen vermeiden           zurück Ein Kapitel tiefer zum Inhaltsverzeichnis

Eine weitere Möglichkeit Threads zu steuern, stellt ein sogenannter Monitor dar. Ein Monitor ist ein Modul, dass bestimmte Daten und Funktionen beinhaltet die beschützt werden müssen. Damit ist es möglich das höchstens ein Thread diese Monitorprozedur ausführt. Dies kennen Sie ja bereits in ähnlicher Form von den Mutexen.

Das Problem dabei ist aber, wenn ein Thread mal keine Arbeit mehr hat, blockiert dieser so lange alle anderen Threads. Dies ist reine Zeitverschwendung wenn andere Threads während dieser Zeit etwas sinnvolles erledigen könnten. Man spricht dabei von race condition.

Zur Lösung dieses Problems wurden sogenannte Condition-Variablen in die Bibliothek implementiert. Somit wartet ein Thread anstatt, auf die Beendigung seiner Arbeit oder eines anderen Threads, auf eine entsprechende Condition-Variable.

Dies können Sie sich bildlich folgendermaßen Vorstellen ...

Ein Thread setzt also den Mutex und überprüft, ob er etwas zu tun hat. Falls nein, wird der Mutex freigegeben und der Thread in eine Wartestellung gesetzt. Dies ist nicht gleich zu setzen mit dem Blockieren. Der Thread wartet nun so lange, bis Ihm ein anderer Thread die Condition-Variable sendet.

Um eine Condition-Variable zu verwenden müssen Sie diese erst initialisieren ...

int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);

Die Funktion zum Warten einer Condition-Variable eines anderen Theads sieht wie folgt aus ...

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timewait(pthread_cond_t *cond,
                          pthread_mutex_t *mutex,struct timespec *exp);

Mit pthread_cond_timewait können Sie im Gegensatz zu pthread_cond_wait ereichen, dass der Thread nicht beliebig lange wartet, sondern nur eine gewisse Zeit. Da Sie .._timewait nicht in diesem Tutorial verwenden werden, folgt hierz ein kurzes Snippet, wie man diese Funktion anwendet ...

#include <sys/time.h> /*Mehr dazu unter man gettimeofday*/
.......
wait.tv_sec = 2;
wait.tv_nsec = 0;
......
pthread_cond_timewait(&cond, &mutex, &wait);
/*Thread wartet 2 Sekunden bis er weiterläuft*/

Nun wartet der Thread zwei Sekunden. Vorausgesetzt natürlich, Sie haben die Condition-Variable initialisiert und ebenso den Mutex.

int pthread_cond_signal(pthread_cond_t *cond)
int pthread_cond_broadcast(pthread_cond_t *cond);

Die Funktion pthread_cond_signal weckt einen Wartenden Thread auf. Welcher das ist liegt leider nicht in Ihrer Hand. Mit der Funktion pthread_cond_broadcast werden alle Threads aufgeweckt, die mit der Condition pthread_cond_wait und pthread_cond_timewait schlafen gelegt wurden bzw. auf die Condition Variable warten.

Beenden können Sie eine Condition-Variable mit der Funktion ...

int pthread_cond_destroy(pthread_cond_t *cond);

Nun wollen wir uns ein einfaches Beispiel dazu ansehen ...

#include <stdio.h>
#include <pthread.h>

pthread_cond_t cond;
pthread_mutex_t mutex;
int werte[10];

/*pthread_cond_t cond = PTHREAD_COND_INITIALIZER;*/

void thread1(void *arg)
{
 int ret,i;
 printf("Thread 1 : %ld gestartet.....\n",pthread_self());
 sleep(1);
 ret=pthread_mutex_lock(&mutex);
 if(ret != 0) {
    printf("Fehler bei lock in Thread : %ld\n",pthread_self());
    exit(0);
   }

 /*Kritischer Codeabschnitt*/
 for(i=0;i<10;i++)
  werte[i]=i;

 printf("Thread 1 : %ld sendet das Signal für die Condition Variable\n");
 pthread_cond_signal(&cond);
 ret=pthread_mutex_unlock(&mutex);
 if(ret != 0) {
    printf("Fehler bei unlock in Thread : %ld\n",pthread_self());
    exit(0);
   }
 printf("Thread 1 : %ld beendet\n");

 pthread_exit((void *)0);
}

void thread2(void *arg)
{
 int ret,i;
 int summe=0;
 printf("Thread 2 : %ld wartet auf die Condition Variable\n",pthread_self());
 pthread_cond_wait(&cond, &mutex);
 printf("Thread 2 : %ld gestartet.....\n",pthread_self());

 for(i=0; i<10; i++)
   summe+=werte[i];

 printf("Thread 2 : %ld beendet\n");
 printf("Summe aller Zahlen beträgt : %d\n",summe);

 pthread_exit((void *)0);
}

int main()
{
 pthread_t th[2];

 pthread_cond_init(&cond,NULL);
 pthread_mutex_init(&mutex, NULL);

 pthread_create(&th[0],NULL, (void *)thread1, NULL);
 pthread_create(&th[1],NULL, (void *)thread2, NULL);

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

 return 0;
}

In diesem Beispiel wartet der Thread Nummer 2 auf die Condition Variable von Thread 1. Thread 1 weist dem globalen Zahlenarray werte, 10 Werte zu, die Thread 2 anschließend berechnet.

Dies ist natürlich auch wieder ein primitives Beispiel und soll nur die Funktion von Condition Variablen demonstrieren. Im Beispiel oben habe ich ...

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

...auskommentiert. Sie können anstatt der Funktion pthread_cond_intit, die global Variable cond gleich mit der Konstante PTHREAD_COND_INITIALIZER definieren.

Wollen wir uns dazu wieder ein Beispiel ansehen. Wir simulieren ein Programm, das Daten empfängt und erzeugen dabei zwei Threads. Jeder dieser beiden Threads wird mit pthread_cond_wait in einem Wartezustand geschickt und warten auf das Signal pthread_cond_signal vom Mainthread.

Dieser simuliert dann, er würde zwei Datenpakete an einem Thread verschicken. Der Thread simuliert anschließend, er würde die Datenpakete bearbeiten. Lassen Sie einfach das Programm ablaufen. Ich denke es erklärt sich von selbst.

#define _MULTI_THREADED
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void checkResults(string, val) {
        if (val) {
             printf("Failed with %d at %s", val, string);
             exit(1);
          }
      }

#define NUMTHREADS 2
pthread_mutex_t dataMutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t dataPresentCondition = PTHREAD_COND_INITIALIZER;
int dataPresent=0;
int sharedData=0;

void *theThread(void *parm)
{
  int rc;
  int retries=2;

  printf("Consumer Thread %.8x : Entered\n", pthread_self());
  rc = pthread_mutex_lock(&dataMutex);
  checkResults("pthread_mutex_lock()\n", rc);

  while (retries--) {
     while (!dataPresent) {
         printf("Consumer Thread %.8x : Warte auf Daten zum bearbeiten\n"
                 ,pthread_self());
         rc = pthread_cond_wait(&dataPresentCondition, &dataMutex);
         if (rc) {
             printf("Consumer Thread %.8x : condwait failed, rc=%d\n"
                     ,rc,pthread_self());
             pthread_mutex_unlock(&dataMutex);
             exit(1);
         }
     }
   printf("Consumer Thread %.8x : Daten wurden gemeldet, "
   " - Bearbeite die Daten sollange sie geschützt sind (lock)\n"
    ,pthread_self());

   if (sharedData==0) {dataPresent=0;}
  }//Ende while
 printf("Consumer Thread %.8x : Alles erledigt\n",pthread_self());
 rc = pthread_mutex_unlock(&dataMutex);
 checkResults("pthread_mutex_unlock()\n", rc);
 return NULL;
}

int main(int argc, char **argv)
{
  pthread_t thread[NUMTHREADS];
  int rc=0;
  int amountOfData=4;
  int i;

  printf("Enter Testcase - %s\n", argv[0]);

  printf("Create/start threads\n");
  for (i=0; i < NUMTHREADS; ++i) {
      rc = pthread_create(&thread[i], NULL, theThread, NULL);
      checkResults("pthread_create()\n", rc);
   }//Ende for

/* The producer loop */
  while (amountOfData--) {
     printf("Producer: Daten gefunden\n");
     sleep(3);

     rc = pthread_mutex_lock(&dataMutex); /* Protect shared data and flag */
     checkResults("pthread_mutex_lock()\n", rc);
     printf("Producer: Sperre die Daten und gib eine Meldung an Consumer\n");
     ++sharedData; /* Add data */
     dataPresent=1; /* Set boolean predicate */

     rc = pthread_cond_signal(&dataPresentCondition); /* wake up a consumer */
     if (rc) {
         pthread_mutex_unlock(&dataMutex);
         printf("Producer: Failed to wake up consumer, rc=%d\n", rc);
         exit(1);
      }

     printf("Producer: Gib die gesperrten Daten und das Flag wieder frei\n");
     rc = pthread_mutex_unlock(&dataMutex);
     checkResults("pthread_mutex_lock()\n",rc);
    } //Ende while

  printf("Wartet bis die Threads beendet sind und Ihre Resourcen freigibt\n");
  for (i=0; i < NUMTHREADS; ++i) {
      rc = pthread_join(thread[i], NULL);
      checkResults("pthread_join()\n", rc);
   }

  printf("Clean up\n");
  rc = pthread_mutex_destroy(&dataMutex);
  rc = pthread_cond_destroy(&dataPresentCondition);
  printf("Main completed\n");
  return 0;
}

Weiter mit 8.6. Threads canceln