ein Kapitel zurück                                           ein Kapitel weiter

Wenn wir mehrere Threads starten und diese parallel ablaufen, können wir nicht erkennen wie weit welcher Thread gerade mit der Verarbeitung von Daten ist. Wenn mehrer Threads Beispielsweise an ein und der selben Aufgabe abhänigig voneinander arbeiten, wird eine Syncronisation 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 Syncronisieren haben wir zwei Möglichkeiten. Zum einen mit sogenannte Locks, die wir in diesem Kapitel mit den Mutexen durchgehen werden und zum anderen ein Monitor. Mit dem Monitor werden sogenannte Condition-Variablen verwendet. Dazu kommen wir in einem der nächsten Kapiteln zurück.

Der Begriff Mutex läßt schlimmeres Erahnen als er wirklich ist. Die Funktionsweise von Mutexen ähnelt dem IPC Semphoren bei den Prozessen, lassen sich 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 das man dafür Verantwortlich ist, keinen "Deadlock" zu erzeugen. In folgenden Fällen könnten bei Threads "Deadlocks" auftreten...

  • Threads können Resourcen anfordern, obwohl sie bereits Resourcen besitzen
  • Ein Thread gibt seine Resource nicht mehr frei
  • eine Resource ist frei oder ist im Besitz eines 'exclusiven' Threads

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

Wir wollen das ganze Anhand eines Bildes Analysieren....




Wir haben hier eine globale Variable i mit dem Wert 1 und 3 Funktionen und 3 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 einzelen Threads mit Hilfe eines Mutexes syncronisiert.

Zuvor nun die Funktionen mit denen sie sogenannte exclusive Sperren einrichten können. Zuerst müssen wir eine Mutex initialisieren, bevor wir Ihn verwenden. Dies erledigen wir 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, muß dieser logischerweise als globale Variable angegeben werden. Auch Mutexen können Attribute vergeben werden (mutexattr). Dazu kommen wir im nächsten Kapitel. Wir begnügen uns zu Beginn wieder mit dem NULL-Zeiger für 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 wir 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 Unterschiede der beiden ist der, daß 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äßt sich nur noch mit "Gewalt" beenden.

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

Hierzu nun ein Programmbeispiel.........

/*Download:thread7.c*/
#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 wurden. Damit sie es aber nicht Mißverstehen, wir haben die Threads nicht Syncronisiert. Sie können ja mal die sleep-Funktion herausnehmen. Wir haben hier nur einen kritischen Codebereich von anderen Threads geschützt.

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

/*Download:thread8.c*/
#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 das läßt 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 alle Threads mit dieser Variable arbeiten ohne Probleme.

Nun 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önen sie diese Attribute mit den Funtkionen....

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

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

  • PTHREAD_MUTEX_FAST_NP - (default-Einstellung) Ein Thread bleibt solange hängen bis er sich beendet.
  • PTHREAD_MUTEX_ERRORCHECK_NP - Programm läuft weiter aber pthread_mutex_lock gibt einen Fehler aus
  • PTHREAD_MUTEX_RECURSIVE_NP - Der Thread wie erneut gesetzt, muss aber daher auch mehrmals freigegeben werden, damit andere Threads nicht blockiert werden.

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

  • PTHREAD_MUTEX_FAST_NP - Mutex wird freigegeben
  • PTHREAD_MUTEX_RECURSIVE_NP - Mutex wird freigegeben
  • PTHREAD_MUTEX_ERRORCHECK_NP - keine Veränderung, aber pthread_mutex_unlock gibt einen Fehler zurück

Um Mutex-Attribute Abfragen oder Setzen zu können müssen sie diese zuerst initialisieren.........

int pthread_mutexattr_init(pthread_mutexattr_t *attr);  

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

  • PTHREAD_MUTEX_RECURSIVE_NP - Wird ein Thread mit diesem Mutex gesperrt, so wird ein Zähler für jede Sperrung um den Wert 1 erhöht. Damit die Sperrung eines rekursiven Mutex aufgehoben wird muss dieser ebenso oft freigegeben werden, wie er gesperrt wurde.

Und hierzu ein Programmbeispiel......

/*Download:thread9.c*/
#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 dasselbe wie im Kapitel zuvor nur mit Beispielen der Funktionen die wir in diesem Kapitel gelernt haben und mit dem Attribut PTHREAD_MUTEX_RECURSIV_NP Und somit wird 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 unser 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 eben auf 0 gestellt worden zu sein. Wenn sie die Mutex-Sperre in der main-Funktion auskommentieren wird diese Aussage bestätigt.

ein Kapitel zurück          nach oben           ein Kapitel weiter


© 2001,2002 Jürgen Wolf