pthread_mutex und pthread_cond



  • Hi,
    ich habe ich ein doofes Producer-/Consumer-Problem, welches sich mit Semaphoren wunderschön lösen ließe, dies aber nicht erlaubt ist. 😛

    Ich benutze pthread_mutex, pthread_cond_t, pthread_mutex_lock(), pthread_cond_wait() und pthread_cond_signal().

    Nun zu meinem Problem:

    Wenn kein Thread auf die Condition-Variable wartet, dann verpufft das Signal von pthread_cond_signal(). Dieses Problem habe ich jeweils, bei den letzten drei Zeilen in der While-Schleife. Ohne sleep() würde der jeweils andere Thread nicht genügend Zeit haben aus dem cond_wait() aufzuwachen und anschließend auf das Signal zu horchen, was einen Deadlock zur Folge hat.
    Meine Frage: Lässt sich das auch ohne sleep() lösen? Vielleicht irgendwie mit pthread_barriers?

    static void *producer( void *arg )
    {
            unsigned long count=0;
            arg = arg; 
    
            while( 1 ) {
                    printf("producer: wait...\n");
                    pthread_mutex_lock( &mutex ); // enter critical section
                    pthread_cond_wait( &cond_consumed, &mutex );
                    printf("producer: PRODUCE %ld...\n", count++);
    
                    pthread_mutex_unlock( &mutex ); // leave critical section
                    sleep(1); // Ohne Slepp Deadlock!
                    pthread_cond_signal( &cond_produced );
            }
            return NULL;
    }
    
    static void *consumer( void *arg )
    {
            unsigned long count=0; 
            arg = arg; 
    
            sleep( 1 );
            pthread_cond_signal( &cond_consumed );
            while( 1 ) {
                    printf("consumer: wait...\n");
                    pthread_mutex_lock( &mutex );
                    pthread_cond_wait( &cond_produced, &mutex );
                    printf("consumer: CONSUME %ld...\n", count++);
    
                    pthread_mutex_unlock( &mutex );
                    sleep(1); // Ohne Slepp Deadlock!
                    pthread_cond_signal( &cond_consumed );
            }
            return NULL;
    }
    

    Danke im Voraus!

    L. G.
    Steffo



  • Das Prinzip ist, dass Du eine separate Bedingung hast, die Du in der critical section prüfst und nur wenn diese nicht gesetzt ist, wartest Du auf das Signal. Und das tust Du in einer Schleife, so lange die Bedingung nicht erfüllt ist. Also etwa so (ungetestet):

    static unsigned dataToConsume = 0;
    
    static void producer()
    {
            unsigned long count=0;
    
            while( 1 ) {
                    printf("producer: PRODUCE %ld...\n", count++);
    
                    printf("producer: wait...\n");
    
                    pthread_mutex_lock( &mutex ); // enter critical section
                    while (dataToConsume > 0)
                          pthread_cond_wait( &cond_consumed, &mutex );
    
                    /* hier zu verarbeitete Daten bereit stellen */
                    ++dataToConsume;
    
                    pthread_mutex_unlock( &mutex ); // leave critical section
                    pthread_cond_signal( &cond_produced );
            }
    }
    
    static void consumer()
    {
            unsigned long count=0;
    
            while( 1 ) {
                    printf("consumer: wait...\n");
                    pthread_mutex_lock( &mutex );
                    while (dataToConsume == 0)
                            pthread_cond_wait( &cond_produced, &mutex );
                    /* hier evetuell Daten vom producer übernehmen */
                    --dataToConsume;
    
                    pthread_mutex_unlock( &mutex );
                    pthread_cond_signal( &cond_consumed );
    
                    printf("consumer: CONSUME %ld...\n", count++);
            }
    }
    

    Hier passiert folgendes:

    Der Producer produziert zunächst etwas, was der consumer Konsumieren soll. Das passiert ohne, dass der Mutex gehalten wird. Wenn er fertig mit Produzieren ist, dann holt er sich den Mutex. Wenn der Consumer mit dem vorigen Daten noch beschäftigt ist, wartet er, bis der consumer signalisiert, dass er fertig ist. Da beim pthread_cond_wait der Mutex während des Wartens frei gegeben wird, kann der Consumer die Variable dataToConsume verändern. Ist die Variable "dataToConsume" auf 0, also der consumer fertig, dann kann der producer die produzierten Daten übergeben und den Mutex dann frei geben. Wenn er den consumer signalisiert hat, dass er Daten bereit gestellt hat, kann der über die Schleife sich den nächsten zu produzierenden Daten zu wenden.

    Der Consumer holt sich sogleich den Mutex, da er ja die geteilte Variable "dataToConsume" lesen will, und so lange keine Daten vom Producer bereit gestellt wurden, wartet er auf das Signal. Wenn der Producer das Signal sendet, kann der Consumer die zu verarbeiteten Daten übernehmen und dem Producer signalisieren, dass er die Daten übernommen hat. Nachdem er den Mutex frei gegeben, kann er die Daten jetzt verarbeiten.

    In diesem Szenario laufen die "CONSUME" und die "PRODUCE"-Sektionen parallel. Du kannst sogar mehrere Producer und mehrere Consumer instantiieren, die dann alle parallel laufen, ohne sich zu verheddern. Nur das Bereitstellen der Daten im producer und das Übernehmen der Daten im consumer muss geschützt ab laufen. Der producer wird in der Regel eine Aufgabe in eine Queue stellen und der Consumer wird diese Aufgabe aus der Queue lesen und entfernen.



  • Hi tntnet,
    danke, das funktioniert wunderbar!!! 😃

    Eine Frage noch: Das Warten auf die Condition-Variable in einer Schleife ist doch nur nötig, wenn es mehr als zwei Producer und Consumer gibt, oder? Eine if-Anweisung würde es bei lediglich einem Consumer und einem Producer auch tun, oder?

    Nochmals danke!

    L. G.
    Steffo



  • Das ist im Prinzip richtig. Es wird üblicherweise mit einer Schleife gemacht und ich empfehle dir das auch so zu tun. Es schadet nicht.

    Normalerweise können Systemaufrufe per Signal beendet werden. In dem Fall ist errno auf EINTR gesetzt. Dann sollte in der Regel der Systemaufruf noch mal ausgeführt werden. Im Fall von pthread_cond_wait ist das zumindest auf Linux nicht der Fall. Die man-page sagt explizit, dass diese Funktion so nicht beendet werden kann. Ob das bei anderen Systemen anders ist, weiß ich nicht. Mit der Schleife bist Du aber immer auf der sicheren Seite.



  • Ah, das ist ein gutes Argument. THX! 😃

    L. G.
    Steffo



  • @tntnet: So n Schwachsinn. Der Grund ist ein Spurious Wakeup. http://en.wikipedia.org/wiki/Spurious_wakeup



  • 314159265358979 schrieb:

    @tntnet: So n Schwachsinn. Der Grund ist ein Spurious Wakeup. http://en.wikipedia.org/wiki/Spurious_wakeup

    Könntest Du Dir nicht endlich mal angewöhnen, Dich ein wenig gepflegter auszudrücken. Es macht keinen Spaß, wenn jemand sich so verhält, wie Du es tust. 😞

    Aber nun zum Thema: die "spurious wakeups": die können dann auftreten, wenn mehrere Threads auf eine Condition warten. Und das speziell bei Multi-Prozessor-Systemen. Da kann es vor kommen, dass mehr als ein Thread geweckt wird. Da schaut der 2. Thread dann in die Röhre und sollte lieber weiter warten.

    Und in diesem speziellen Fall haben wir aber genau einen Thread, der auf die Condition wartet. Da kann dieses spruious wakeup nicht passieren. Dennoch lautet ja meine Empfehlung, dass man das in einer Schleife machen sollte.

    Ich empfehle auch die Lektüre der man page von pthread_cond_wait unter Linux. Da gibt es einen Abschnitt "Condition Wait Semantics" da ist es auch nochmal erklärt. Und die man page ist immer die bessere Quelle als Wikipedia.



  • Wenn du schon die man-Page empfiehlst, dann solltest du sie wenigstens gelesen haben.

    In general, whenever a condition wait returns, the thread has to re-evaluate the predicate associated with the condition wait to determine whether it can safely proceed, should wait again, or should declare a timeout. A return from the wait does not imply that the associated predicate is either true or false.

    Und ob dir mein Verhalten passt oder nicht, geht mir so ziemich hintenrum vorbei. Sei eben nicht so empfindlich. :p



  • 314159265358979 schrieb:

    Und ob dir mein Verhalten passt oder nicht, geht mir so ziemich hintenrum vorbei. Sei eben nicht so empfindlich. :p

    Und das von dir, wer weint denn immer rum dass alle so gemein zu ihm sind.



  • Hast schon Recht, such irgendwas, um mich zu kritisieren. xDDD

    Srsly, ich hab in diesem Thread einfach Recht, also GTFO 😉



  • Genug.


Anmelden zum Antworten