-1
votes

Est-il nécessaire de verrouiller?

J'ai rencontré une question dans Leetcode . J'ai consulté des solutions dans le Discutez . Mais mon Solution est différent des autres parce que je n'utilise pas Le verrouillage code> dans la méthode d'abord code>. Je me demande si mon code est correct. En outre, pouvez-vous me donner des conseils sur mon code?

Je pense qu'il n'est pas nécessaire d'utiliser unique_lock code> dans la méthode vide d'abord (fonction PrintFirst) Code > Comme void deuxième (fonction impriméseconde) code>, est-ce correct? P>

class Foo {
public:
    Foo() {

    }

    void first(function<void()> printFirst) {
        // cout<<1<<endl;
        // printFirst() outputs "first". Do not change or remove this line.
        // mtx.lock();
        printFirst();
        flag=1;
        // mtx.unlock();
        cond.notify_all();
        // cout<<11<<endl;
    }

    void second(function<void()> printSecond) {
        // cout<<2<<endl;
        {
            unique_lock<mutex> lk(mtx);
            cond.wait(lk,[this](){return flag==1;});
            // printSecond() outputs "second". Do not change or remove this line.
            printSecond();
            flag=2;
        }

        // cout<<22<<endl;
        cond.notify_all();
    }

    void third(function<void()> printThird) {
        // cout<<3<<endl;
        unique_lock<mutex> lk(mtx);
        cond.wait(lk,[this](){return flag==2;});
        // printThird() outputs "third". Do not change or remove this line.
        printThird();
        flag=3;
        // cout<<33<<endl;
    }

    mutex mtx;
    condition_variable cond;
    int flag=0;
};


1 commentaires

Veuillez poster un exemple de reproductible minimal . Au fur et à mesure que votre classe ne contient aucun threads, cela n'a pas besoin de mutiles, cependant, si votre classe est appelée à partir de threads multiples, cela pourrait nécessiter des mutiles.


3 Réponses :


1
votes

C'est nécessaire.

que votre code produit une sortie correcte dans cette instance est en plus du point. Il est tout à fait éventuellement que printfirst pourrait ne pas être complet à l'heure imprimésecond est appelé. Vous avez besoin du mutex pour éviter cela et arrêter imprimésecond et printThird . d'être couru en même temps.


6 commentaires

Je ne peux pas être d'accord. Même si imprimésecond est appelé d'abord, il sera bloqué jusqu'à ce que PrintFirst Terminé. @QctDev


ahh je vois le cond.Wait maintenant. Avec cela, vous n'aurez pas besoin du mutex ou des autres serrures. Je préférerais le paradigme Mutex / Lock, car votre intention est plus évidente.


@QctDev Il aura toujours besoin du mutex pour s'assurer que l'écriture et la lecture de drapeau ne se produit pas en même temps.


@Tedlyngmo Si c'est juste drapeau laissé pour protéger puis drapeau doit être effectué atomique .


@QCTDev qui introduirait un niveau de verrouillage sur le mutex déjà existant censé protéger drapeau que second et troisième utilise. La bonne façon de le faire serait d'utiliser le même motif de verrouillage dans d'abord comme utilisé dans secondaire et troisième .


@Tedlyngmo Je ne dis pas que ce n'est pas le cas. Nous convenons que le mutex est la solution optimale.



2
votes

Évidemment, vos fonctions de trois éléments sont censées être appelées par différents threads. Ainsi, vous devez verrouiller le mutex dans chaque thread pour protéger la variable commune indicateur de l'accès simultané. Donc, vous devriez noter mtx.lock () et mtx.unlock () dans premier pour le protéger également. Fonctions secondes et troisième Appliquez un unique_lock comme alternative pour cela.

Assurez-vous toujours de déverrouiller le mutex avant d'appeler cond.notify_all () soit en appelant mtx.unlock () avant ou en faisant le unique_lock une variable locale d'un bloc de code interne comme dans second .

Conseil supplémentaire

mettre un privé: avant les variables d'élément au bas de la définition de votre classe pour les protéger de l'accès extérieur. Cela garantira que indicateur ne peut pas être modifié sans verrouiller le mutex.


12 commentaires

Bon conseil +1.


"Assurez-vous toujours de déverrouiller le mutex avant d'appeler Cond.Notify_All ()" - Pourquoi? Afaik n'est pas nécessaire.


@sklott "Le fil notifiant n'a pas besoin de maintenir le verrou sur le même mutex que celui détenu par le ou les filets d'attente; en fait le cas d'une pessimisation, puisque le fil notifié bloquera immédiatement, en attente de la notification fil pour libérer la serrure. "


"Pas besoin de" et "ne devrait pas être" est un peu différent, hein?


@sklott Ouais la déclaration est vague dans la réponse.


Et " notifiant pendant que, sous la serrure, peut néanmoins être nécessaire lorsque la planification précise des événements est requise, par exemple si le fil d'attente quitte le programme si la condition est satisfaite, causant la destruction de la condition de filetage de notification. Un réveil parasite après le mutex. Déverrouiller mais avant de notifier n'entraînerait pas l'avertissement appelé un objet détruit. "- ces réveilles parasites sont terribles


Si je ne verrouille pas le mutex, y a-t-il une erreur logique? Pouvez-vous faire un exemple? MERCI!


@Alfred Si vous ne verrouillez pas le mutex, vous pouvez attribuer une valeur à drapeau en même temps qu'un autre thread se lit drapeau comme je l'ai expliqué dans ma réponse.


@Alfred: Imaginez votre drapeau est par exemple un énorme vecteur et une fonction va changer tous les éléments. Maintenant, lorsque cette fonction a changé la moitié des éléments, une autre fonction dans un autre thread est exécutée et lit la moitié des valeurs anciennes et la moitié des nouvelles valeurs, ce qui serait incompatible. Le mutex empêchera cela. Bien qu'il soit improbable que cela arrive à votre drapeau , il peut également se produire et serait un code risqué de ne pas empêcher cela.


J'ai compris! Merci!


Grande note sur l'obtention du mutex. Les variables partagées protégées par un mutex doivent utiliser le mutex sur chaque accès, même si ce n'est pas évidemment nécessaire, car il empêche également le compilateur et la CPU d'optimiser les lectures de la variable et le cache de rétablissement de la mémoire principale.


Le signal d'appel en dehors de la section critique est un bon conseil, mais peut ne pas entraîner la meilleure performance. Il devrait théoriquement empêcher un sommeil supplémentaire. Cependant, le système d'exploitation peut tenter de planifier "intelligemment". Mettre fin à la section critique (en déverrouillant le mutex) peut être perçue comme une bonne opportunité pour un commutateur de contexte. Dans ce cas, le thread sera préempté avant d'appelle signal , entraînant un peu plus long de retard avant que le fil d'attente puisse s'exécuter. Je conseillerais toujours de signaler à l'extérieur du mutex, avec la mise en garde qu'il n'est pas nécessaire et non toujours le meilleur plan d'action.



1
votes

Le drapeau Vérification de la condition dans secondaire () ou troisième () peut être évalué en même temps que premier () attribue 1 à indicateur .

réécrire ce xxx

comme ça et il peut être plus facile Pour voir: xxx

Il fait la même chose que votre attendre () avec un lambda.

drapeau est censé être lu alors que le mutex est maintenu - mais premier ne se soucie pas des mutiles et des attributs à drapeau chaque fois qu'il veut. Pour les types non atomiques, c'est une catastrophe. Il peut travailler 10000000 fois (et probablement) - mais quand les choses se produisent en même temps (parce que vous le laissez) - boom - comportement non défini < / p>


2 commentaires

Même les types atomiques présentent cette condition de course


@Alanbirtles, je me faisais simplement pointer sur la course lors de ses opérations de R / W simultanées dans le même lieu de mémoire qui conduirait à UB - et que c'était un atomique, cela ne se produirait pas.