web-dev-qa-db-fra.com

Est-ce que pthread_cond_wait (& cond_t, & mutex); déverrouiller puis verrouiller le mutex?

J'utilise pthread_cond_wait(&cond_t, &mutex); dans mon programme et je me demande pourquoi cette fonction a besoin comme second paramètre d'une variable mutex.

La pthread_cond_wait() déverrouille-t-elle le mutex au début (début de l'exécution pthread_cond_wait()) puis verrouillée à la fin (juste avant de quitter pthread_cond_wait())?

38
MOHAMED

Lorsque le premier thread appelle pthread_cond_wait(&cond_t, &mutex); il libère le mutex et attend la condition cond_t est signalé comme terminé et mutex est disponible.

Donc quand pthread_cond_signal est appelé dans l'autre thread, il ne "réveille" pas le thread qui attend encore. mutex doit être déverrouillé en premier, seulement alors il y a une chance que le premier thread obtienne un verrou, ce qui signifie que "lors du retour réussi de pthread_cond_wait mutex doit avoir été verrouillé et appartenir au thread appelant. "

33
LihO

Il y a beaucoup texte sur le sujet des variables de condition et leur utilisation, donc je ne vous ennuierai pas avec une tonne de détails moches. La raison pour laquelle ils existent est de vous permettre de notifier le changement dans un état prédicat. Les éléments suivants sont critiques pour comprendre la bonne utilisation des variables de condition et leur association mutex:

  • pthread_cond_wait() simultanément déverrouille le mutex et commence à attendre que la variable de condition soit signalée. vous devez donc toujours avoir la propriété du mutex avant de l'invoquer.

  • pthread_cond_wait() retourne avec le mutex verrouillé, vous devez donc déverrouiller le mutex pour permettre son utilisation ailleurs une fois terminé. Si le retour s'est produit parce que la variable de condition a été signalée ou non n'est pas pertinent. Vous devez toujours vérifier votre prédicat indépendamment pour tenir compte des potentiels réveils parasites.

  • Le but du mutex est pas pour protéger la variable de condition; c'est pour protéger le prédicat sur lequel la variable de condition est utilisée comme mécanisme de signalisation. C'est de loin l'idiome le plus souvent mal compris des variables de condition pthread et de leurs mutex. La variable de condition n'a pas besoin d'une protection d'exclusion mutuelle; les données de prédicat le fait. Considérez le prédicat comme un état extérieur qui est surveillé par les utilisateurs de la paire variable-condition/mutex.

Par exemple, un élément trivial mais évidemment faux pour attendre un indicateur booléen fSet:

bool fSet = false;

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

Je devrais être évident que le problème principal est que le prédicat, fSet, n'est pas du tout protégé. Beaucoup les choses peuvent mal tourner ici. Ex: à partir du moment où vous évaluez la condition de temps jusqu'au moment où vous commencez à attendre (ou à tourner, ou autre), la valeur peut avoir changé. Si cette notification de changement est en quelque sorte manquée, vous attendez inutilement.

Nous pouvons changer cela un peu pour qu'au moins le prédicat soit protégé d'une manière ou d'une autre. L'exclusion mutuelle dans les deux modifications et évaluation du prédicat est facilement fournie avec (quoi d'autre) un mutex.

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
bool fSet = false;

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

Eh bien, cela semble assez simple .. Maintenant, nous n'évaluons jamais le prédicat sans d'abord y avoir un accès exclusif (en verrouillant le mutex). Mais cela reste un problème majeur. Nous avons verrouillé le mutex, mais nous ne le libérons jamais tant que notre boucle n'est pas terminée. Si tout le monde respecte les règles et attend le verrou mutex avant l'évaluation ou la modification de fSet, il ne pourra jamais le faire tant que nous n'aurons pas abandonné le mutex. Le seul "quelqu'un" qui peut le faire dans ce cas est nous.

Alors qu'en est-il de l'ajout de couches supplémentaires? Est-ce que ça va marcher?

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);
}

Eh bien, oui, cela "fonctionnera", mais ce n'est pas beaucoup mieux. La période entre XXXXX et YYYYY nous ne possédons pas le mutex (ce qui est correct, car nous ne vérifions ni ne modifions fSet de toute façon). Mais à tout moment pendant cette période, un autre thread peut (a) obtenir le mutex, (b) modifier fSet, et (c) libérer le mutex, et nous n'en saurons rien tant que nous n'aurons pas terminé notre sleep(), obtenez à nouveau le verrou mutex et faites une boucle pour une autre vérification.

Il a pour être un meilleur moyen. D'une manière ou d'une autre, il devrait y avoir un moyen de libérer le mutex et commencer à attendre une sorte de signal qui nous indique qu'un changement dans le prédicat peut s'être produit. Tout aussi important, lorsque nous recevons ce signal et retournons à notre code, nous devrions déjà posséder le verrou qui nous donne accès pour vérifier les données de prédicat. C'est exactement ce qu'une variable de condition est conçue pour fournir.


La variable de condition en action

Saisissez la paire condition-variable + mutex. Le mutex protège l'accès au changement de ou de vérification du prédicat, tandis que la variable de condition met en place un système de surveillance d'un changement, et plus important encore, de le faire atomiquement ( en ce qui vous concerne, de toute façon) avec l'exclusion mutuelle sous-jacente:

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);
}

Pour un autre thread pour signaler la boucle ci-dessus, il existe plusieurs façons de le faire, les deux plus populaires ci-dessous:

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

Autrement...

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

Chacun a un comportement intrinsèque différent et je vous invite à faire quelques devoirs sur ces différences et à déterminer ce qui est le plus approprié pour des circonstances spécifiques. Le premier offre un meilleur flux de programme au détriment de l'introduction de potentiellement réveils injustifiés. Ce dernier réduit ces réveils mais au prix d'une moindre synergie de contexte. Soit fonctionnera dans notre exemple, et vous pouvez expérimenter comment chacun affecte vos boucles d'attente. Quoi qu'il en soit, une chose est primordiale et les méthodes les deux remplissent ce mandat:

Ne changez jamais, ni vérifiez, la condition de prédicat à moins que le mutex ne soit verrouillé. Jamais.


Fil de moniteur simple

Ce type d'opération est courant dans un thread moniteur qui agit sur une condition de prédicat spécifique, qui (sans vérification d'erreur) ressemble généralement à ceci:

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;
}

n fil de surveillance plus complexe

Modifier ce formulaire de base pour tenir compte d'un système notification qui ne vous oblige pas à garder le mutex verrouillé une fois que vous avez récupéré la notification devient un peu plus compliqué, mais pas beaucoup. Ci-dessous est un proc de moniteur qui ne garde pas le mutex verrouillé pendant le traitement régulier une fois que nous avons établi que nous avons été servis (pour ainsi dire).

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;
}

Où quelqu'un utiliserait-il quelque chose comme ça? Supposons que votre "prédicat" soit l '"état" d'une file d'attente de travail ainsi qu'un indicateur pour vous dire d'arrêter la boucle et de quitter. Lorsque vous recevez la notification que quelque chose est "différent", vous vérifiez si vous devez continuer à exécuter votre boucle et, si vous décidez de continuer, supprimez certaines données de la file d'attente. La modification de la file d'attente nécessite le verrouillage du mutex (rappelez-vous, son "état" fait partie de notre prédicat). Une fois que nous avons extrait nos données, nous les avons localement et pouvons les traiter indépendant de l'état de la file d'attente, donc nous libérons le mutex, faisons notre chose, puis demandons le mutex pour la prochaine remise des gaz . Il existe de nombreuses façons de coder le concept ci-dessus, y compris une utilisation judicieuse de pthread_cond_broadcast, Etc. Mais la forme de base est, espérons-le, compréhensible.

Cela s'est avéré être beaucoup plus long que je ne l'avais espéré, mais c'est un obstacle majeur pour les personnes qui apprennent la programmation pthread, et je pense que cela vaut le temps/l'effort supplémentaire. J'espère que vous en avez tiré quelque chose.

113
WhozCraig

oui, il se déverrouille, attend que la condition soit remplie, puis attend jusqu'à ce qu'il puisse réacquérir le mutex passé.

6
Daij-Djan