Funktioniert pthread_cond_wait(&cond_t, &mutex); Mutex entsperren und dann sperren?

Lesezeit: 12 Minuten

Ich benutze pthread_cond_wait(&cond_t, &mutex); in meinem Programm und ich frage mich, warum diese Funktion als zweiten Parameter eine Mutex-Variable benötigt.

Tut das pthread_cond_wait() Entsperren Sie den Mutex am Anfang (Beginn der Ausführung pthread_cond_wait()) und dann gesperrt, wenn es fertig ist (kurz vor dem Verlassen pthread_cond_wait())?

Benutzeravatar von WhozCraig
WhozCraig

Es gibt viele Text zum Thema Bedingungsvariablen und deren Verwendung, damit ich Sie nicht mit hässlichen Details langweile. Der Grund, warum sie überhaupt existieren, ist, Ihnen zu ermöglichen, Änderungen in a mitzuteilen Prädikat Zustand. Die Folgenden sind kritisch beim Verständnis der richtigen Verwendung von Bedingungsvariablen und ihrer Mutex-Assoziation:

  • pthread_cond_wait() gleichzeitig entsperrt der Mutex und beginnt mit dem Warten auf die zu signalisierende Bedingungsvariable. also musst du stets Eigentümer des Mutex sein, bevor Sie ihn aufrufen.

  • pthread_cond_wait() kehrt mit dem Mutex zurück gesperrt, daher müssen Sie den Mutex entsperren, um ihn an anderer Stelle verwenden zu können, wenn Sie damit fertig sind. Ob die Rückgabe erfolgte, weil die Bedingungsvariable signalisiert wurde oder nicht ist nicht relevant. Sie müssen Ihr Prädikat trotzdem überprüfen, um das Potenzial zu berücksichtigen falsches Aufwachen.

  • Der Zweck des Mutex ist nicht um die Bedingungsvariable zu schützen; es ist zu schützen Prädikat auf dem die Bedingungsvariable als Signalisierungsmechanismus verwendet wird. Dies ist zweifellos die am häufigsten missverstandene Redewendung von pthread-Bedingungsvariablen und ihren Mutexe. Die Bedingungsvariable benötigt keinen gegenseitigen Ausschlussschutz; die Prädikatsdaten tut. Stellen Sie sich das Prädikat als einen Außenzustand vor, der von den Benutzern des Bedingungsvariable/Mutex-Paares überwacht wird.

Zum Beispiel ein triviales, aber offensichtliches falsch Stück Code, um auf ein boolesches Flag zu warten fSet:

bool fSet = false;

int WaitForTrue()
{
    while (!fSet)
    {
        sleep(n);
    }
}

Ich sollte offensichtlich sein, dass das Hauptproblem das Prädikat ist, fSetist überhaupt nicht geschützt. Viele hier kann was schief gehen. Beispiel: Von dem Zeitpunkt an, an dem Sie die While-Bedingung auswerten, bis zu dem Zeitpunkt, an dem Sie mit dem Warten (oder Drehen oder was auch immer) beginnen, kann sich der Wert geändert haben. Wenn die Benachrichtigung irgendwie geändert wird verpasstSie warten unnötig.

Wir können dies ein wenig ändern, damit zumindest das Prädikat irgendwie geschützt ist. Gegenseitiger Ausschluss bei beiden Modifizieren und die Auswertung des Prädikats wird einfach mit (was sonst) einem Mutex versehen.

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
bool fSet = false;

int WaitForTrue()
{
    pthread_mutex_lock(&mtx);
    while (!fSet)
        sleep(n);
    pthread_mutex_unlock(&mtx);
}

Nun, das scheint einfach genug zu sein. Jetzt werten wir das Prädikat niemals aus, ohne zuerst exklusiven Zugriff darauf zu erhalten (indem wir den Mutex verriegeln). Aber das ist immer noch ein großes Problem. Wir haben den Mutex verriegelt, aber wir geben es nie frei, bis unsere Schleife beendet ist. Wenn sich alle anderen an die Regeln halten und auf die Mutex-Sperre warten, bevor sie ausgewertet oder geändert werden fSet, das können sie erst, wenn wir den Mutex aufgeben. Der einzige “Jemand”, der das in diesem Fall tun kann, ist uns.

Wie wäre es also, wenn Sie noch mehr Schichten hinzufügen? Ob das funktioniert?

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
bool fSet = false;

int WaitForTrue()
{
    pthread_mutex_lock(&mtx);
    while (!fSet)
    {
        pthread_mutex_unlock(&mtx);
        // XXXXX
        sleep(n);
        // YYYYY
        pthread_mutex_lock(&mtx);
    }
    pthread_mutex_unlock(&mtx);
}

Nun ja, es wird “funktionieren”, aber es ist immer noch nicht viel besser. Die Zeit dazwischen XXXXX und YYYYY wir besitzen den Mutex nicht (was in Ordnung ist, da wir ihn nicht überprüfen oder modifizieren fSet ohnehin). Aber jederzeit während dieser Zeit kann ein anderer Thread (a) den Mutex erhalten, (b) modifizieren fSetund (c) den Mutex freigeben, und wir werden nichts darüber wissen, bis wir unseren beendet haben sleep()rufen Sie erneut die Mutex-Sperre ab und machen Sie eine Schleife für eine weitere Überprüfung.

Dort hat ein besserer Weg zu sein. Irgendwie sollte es eine Möglichkeit geben, den Mutex freizugeben und Beginnen Sie damit, auf eine Art Signal zu warten, das uns mitteilt, dass möglicherweise eine Änderung im Prädikat stattgefunden hat. Ebenso wichtig ist, dass wir, wenn wir dieses Signal erhalten und zu unserem Code zurückkehren, bereits die Sperre besitzen sollten, die uns Zugriff gewährt, um die Prädikatdaten zu überprüfen. Das ist exakt was eine Bedingungsvariable liefern soll.


Die Bedingungsvariable in Aktion

Geben Sie das Bedingungsvariable + Mutex-Paar ein. Der Mutex schützt den Zugriff auf das Ändern oder prüfen das Prädikat, während die Bedingungsvariable ein System zur Überwachung einer Änderung einrichtet, und was noch wichtiger ist, dies zu tun atomar (jedenfalls was Sie betrifft) mit dem Prädikat gegenseitiger Ausschluss:

int WaitForPredicate()
{
    // lock mutex (means:lock access to the predicate)
    pthread_mutex_lock(&mtx);

    // we can safely check this, since no one else should be 
    // changing it unless they have the mutex, which they don't
    // because we just locked it.
    while (!predicate)
    {
        // predicate not met, so begin waiting for notification
        // it has been changed *and* release access to change it
        // to anyone wanting to by unlatching the mutex, doing
        // both (start waiting and unlatching) atomically
        pthread_cond_wait(&cv,&mtx);

        // upon arriving here, the above returns with the mutex
        // latched (we own it). The predicate *may* be true, and
        // we'll be looping around to see if it is, but we can
        // safely do so because we own the mutex coming out of
        // the cv-wait call. 
    }

    // we still own the mutex here. further, we have assessed the 
    //  predicate is true (thus how we broke the loop).

    // take whatever action needed. 

    // You *must* release the mutex before we leave. Remember, we
    //  still own it even after the code above.
    pthread_mutex_unlock(&mtx);
}

Für einen anderen Thread, um die obige Schleife zu signalisieren, gibt es mehrere Möglichkeiten, dies zu tun, die beiden beliebtesten unten:

pthread_mutex_lock(&mtx);
TODO: change predicate state here as needed.
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cv);

Ein anderer Weg…

pthread_mutex_lock(&mtx);
TODO: change predicate state here as needed.
pthread_cond_signal(&cv);
pthread_mutex_unlock(&mtx);

Jeder hat ein anderes intrinsisches Verhalten, und ich lade Sie ein, einige Hausaufgaben zu diesen Unterschieden zu machen und festzustellen, was für bestimmte Umstände besser geeignet ist. Ersteres bietet einen besseren Programmfluss auf Kosten der Einführung möglicherweise ungerechtfertigtes Aufwachen. Letzteres reduziert diese Wake-Ups, jedoch zum Preis von weniger Kontextsynergien. Entweder wird in unserem Beispiel funktionieren, und Sie können damit experimentieren, wie sich jeder auf Ihre Warteschleifen auswirkt. Unabhängig davon, eine Sache von größter Bedeutung, und beide Methoden erfüllen diesen Auftrag:

Nie ändern, auch nicht prüfendie Prädikatbedingung, es sei denn, der Mutex ist gesperrt. Je.


Einfacher Monitor-Thread

Diese Art der Operation ist in a üblich Monitor Thread, der auf eine bestimmte Prädikatbedingung einwirkt, die (ohne Fehlerprüfung) normalerweise so aussieht:

void* monitor_proc(void *pv)
{
    // acquire mutex ownership
    //  (which means we own change-control to the predicate)
    pthread_mutex_lock(&mtx);

    // heading into monitor loop, we own the predicate mutex
    while (true)
    {
        // safe to check; we own the mutex
        while (!predicate)
            pthread_cond_wait(&cv, &mtx);

        // TODO: the cv has been signalled. our predicate data should include
        //  data to signal a break-state to exit this loop and finish the proc,
        //  as well as data that we may check for other processing.
    }

    // we still own the mutex. remember to release it on exit
    pthread_mutex_unlock(&mtx);
    return pv;
}

Ein komplexerer Monitor-Thread

Modifikation dieser Grundform, um a zu berücksichtigen Benachrichtigung System, das nicht erfordert, dass Sie den Mutex verriegelt halten, sobald Sie die Benachrichtigung abgeholt haben, wird ein wenig komplizierter, aber nicht sehr viel. Unten ist ein Monitor-Proc, der den Mutex während der regulären Verarbeitung nicht zwischengespeichert hält, sobald wir festgestellt haben, dass wir bedient wurden (sozusagen).

void* monitor_proc(void *pv)
{
    // acquire mutex ownership
    //  (which means we own change-control to the predicate)
    pthread_mutex_lock(&mtx);

    // heading into monitor loop, we own the predicate mutex
    while (true)
    {
        // check predicate
        while (!predicate)
            pthread_cond_wait(&cv, &mtx);

        // some state that is part of the predicate to 
        // inform us we're finished
        if (break-state)
            break;

        // TODO: perform latch-required work here.

        // unlatch the mutex to do our predicate-independant work.
        pthread_mutex_unlock(&mtx);

        // TODO: perform no-latch-required work here.

        // re-latch mutex prior to heading into wait
        pthread_mutex_lock(&mtx);            
    }

    // we still own the mutex. remember to release it on exit
    pthread_mutex_unlock(&mtx);
    return pv;
}

Wo würde jemand so etwas verwenden das ? Angenommen, Ihr “Prädikat” ist der “Zustand” einer Arbeitswarteschlange sowie ein Flag, das Ihnen sagt, dass Sie die Schleife stoppen und beenden sollen. Nachdem Sie die Benachrichtigung erhalten haben, dass etwas „anders“ ist, prüfen Sie, ob Sie mit der Ausführung Ihrer Schleife fortfahren sollten, und entscheiden, dass Sie fortfahren sollten, und entfernen einige Daten aus der Warteschlange. Das Ändern der Warteschlange erfordert, dass der Mutex verriegelt wird (denken Sie daran, dass sein “Zustand” Teil unseres Prädikats ist). Sobald wir unsere Daten gepoppt haben, haben wir sie lokal und können sie verarbeiten unabhängig des Warteschlangenzustands, also geben wir den Mutex frei, machen unser Ding und fordern dann den Mutex für das nächste Durchstarten an. Es gibt viele Möglichkeiten, das obige Konzept zu codieren, einschließlich der vernünftigen Verwendung von pthread_cond_broadcastusw. Aber die Grundform ist hoffentlich verständlich.

Dies stellte sich als erheblich länger heraus, als ich gehofft hatte, aber dies ist eine Haupt Hürde für Leute, die pthread-Programmierung lernen, und ich denke, es ist die zusätzliche Zeit/Mühe wert. Ich hoffe, du hast etwas davon mitbekommen.

  • In deiner WaitForPredicate Beispiel, die zweite Überprüfung für if (predicate) ist überflüssig. Es ist bekannt, dass es an diesem Punkt wahr ist (andernfalls würden Sie immer noch eine Schleife ausführen.) und sollte sich unter Ihnen nicht ändern (vorausgesetzt, der Signalisierungscode setzt die Bedingung und signalisiert unter dem Mutex, was er tun sollte).

    – Hasturkun

    17. Februar 2013 um 20:03 Uhr

  • @Hasturkun Guter Fang. Ich habe schon an die nächsten beiden Samples gedacht, als ich das geschrieben habe. Danke, dass du das für mich aufgehoben hast. Ich bin es so gewohnt, dies in Schleifen zu tun, in denen Sie immer das Prädikat überprüfen nach Das Warten wird sowieso signalisiert, ich distanziere mich komplett vom Single-Run-Fall. Duh.

    – WhozCraig

    17. Februar 2013 um 20:06 Uhr


  • Vielen Dank für eine sehr gute Erklärung; Ich glaube, ich habe endlich den Typ pthread_cond_t verstanden.

    – Benutzer422005

    17. Februar 2013 um 21:30 Uhr

  • Dies ist eine der besten Antworten, die ich je gesehen habe. Ich danke dir sehr!

    – Vatsal Manot

    5. Oktober 2015 um 14:16 Uhr

  • @HarlanChen Betrachten Sie den Fall der Signalisierung nach die Freischaltung. Angenommen, der Mutex ist entsperrt, aber das Signal wurde noch nicht aufgerufen. Nehmen wir nun an, ein anderer Thread wurde ausgeführt, hat den Mutex abgerufen (was er tun kann, weil niemand sonst ihn hält), das Prädikat überprüft und festgestellt, dass es gesetzt ist, also weitermarschiert, anstatt in eine Wartezeit zu verfallen. Dann, endlich der ursprüngliche Thread ruft das Signal auf. Der Aufruf wäre überflüssig, kann aufwachen Ein weiterer Thread, aber jetzt ist das Prädikat nicht mehr wahr (Fall: ein einzelnes Element, das in eine Arbeitswarteschlange verschoben wird). Denken Sie darüber nach, wie die Signalisierung vor dem Entsperren dies ändert.

    – WhozCraig

    18. März 2018 um 6:06 Uhr


Benutzeravatar von LihO
Liho

Wenn der erste Thread ruft pthread_cond_wait(&cond_t, &mutex); Es gibt den Mutex frei und wartet auf die Bedingung cond_t wird als abgeschlossen gemeldet und mutex ist verfügbar.

Also wann pthread_cond_signal im anderen Thread aufgerufen wird, “weckt” er den wartenden Thread noch nicht auf. mutex muss zuerst entsperrt werden, nur dann besteht die Möglichkeit, dass der erste Thread eine Sperre erhält, was bedeutet, dass “bei erfolgreicher Rückgabe von pthread_cond_wait Mutex muss gesperrt worden sein und dem aufrufenden Thread gehören.”

Benutzeravatar von Daij-Djan
Daij-Djan

Ja, es entsperrt, wartet auf die Erfüllung der Bedingung und wartet dann, bis es den übergebenen Mutex abrufen kann.

  • Es entsperrt also den Mutex am Anfang und sperrt dann, wenn er fertig ist. ist nicht?

    – MOHAMED

    17. Februar 2013 um 18:29 Uhr


  • @MohamedKALLEL Noch wichtiger ist es atomar sowohl im Unlock- als auch im Begin-Wait-Zustand. Das ist überhaupt der grundlegende Grund für Bedingungsvariablen und den dazugehörigen Mutex.

    – WhozCraig

    17. Februar 2013 um 18:36 Uhr

  • Um zu verdeutlichen, was “atomar” bedeutet, bedeutet dies, dass der Thread zu dem Zeitpunkt, zu dem ein anderer Thread den Mutex als entsperrt (dh den Mutex sperren) beobachten kann, aufgerufen wird pthread_cond_wait ist schon auf der Bedingungsvariable blockiert. Daher können keine Änderungen an dem durch den Mutex geschützten Zustand vorgenommen werden, bis der aufrufende Thread bereits blockiert ist, und jede zukünftige Signalisierung, die nach dem Vornehmen solcher Änderungen erfolgt, wird notwendigerweise den blockierten Thread als einen Kandidaten zum Aufwecken enthalten.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    17. Februar 2013 um 18:44 Uhr

  • @R sehr gut ausgedrückt. Ich hoffe, Mohamed liest das sorgfältig, weil es so ist Ader zur Verwendung von Condvars und Mutex-Paaren.

    – WhozCraig

    17. Februar 2013 um 19:07 Uhr

  • Dazu kommt die Verwendung von timer_create() des POSIX-Intervall-Timers und die Einstellung sigev_notify = SIGEV_THREAD; Überrascht, dass der Signal-Thread den Haupt-Thread vorwegnimmt, eine sofortige Ausnahmeverarbeitung zulässt, aber bei der Rückkehr die Verarbeitung automatisch wie von Zauberhand genau dort wieder aufnimmt, wo sie aufgehört hat. Ich kann mich nicht erinnern, dass kritische Abschnitte der alten Schule so sauber waren. Außerdem scheinen Threads im Vergleich zur Verwendung von Signalen viel einfacher und nützlicher zu sein.

    – Benutzer2548100

    5. November 2013 um 23:24 Uhr


1409230cookie-checkFunktioniert pthread_cond_wait(&cond_t, &mutex); Mutex entsperren und dann sperren?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy