Quand une variable de condition est-elle nécessaire, un mutex n'est-il pas suffisant?
je suis sûr que mutex n'est pas suffisant c'est la raison pour laquelle le concept de variables de condition existe; mais il me bat et je ne suis pas en mesure de me convaincre avec un scénario concret quand une variable de condition est essentielle.
différences entre les variables conditionnelles, les mutex et les serrures la réponse acceptée de la question dit qu'une variable de condition est un
serrure avec un mécanisme de "signalisation". Il est utilisé lors de la les threads ont besoin de attendre qu'une ressource soit disponible. Un fil peut "attendre" sur un CV et puis le producteur de ressources peut "signaler" la variable, dans laquelle cas les threads qui attendent le CV sont avertis et peuvent continuer exécution
où je suis confus est que, un thread peut attendre sur un mutex aussi, et quand il est signalé, est signifie simplement que la variable est maintenant disponible, pourquoi aurais-je besoin d'une variable de condition?
P. S.: aussi, un mutex est nécessaire pour garder la variable de condition de toute façon, quand rend ma vision plus difficile vers ne pas voir le but de la variable de condition.
6 réponses
même si vous pouvez les utiliser comme vous le décrivez, les Mutex n'ont pas été conçus pour être utilisés comme un mécanisme de notification/synchronisation. Elles visent à fournir un accès mutuellement exclusif à une ressource partagée. Utiliser des Mutex pour signaler une condition est maladroit et je suppose que ressemblerait à quelque chose comme ceci (où Thread1 est signalé par Thread2):
Thread1:
while(1) {
lock(mutex); // Blocks waiting for notification from Thread2
... // do work after notification is received
unlock(mutex); // Tells Thread2 we are done
}
Thread2:
while(1) {
... // do the work that precedes notification
unlock(mutex); // unblocks Thread1
lock(mutex); // lock the mutex so Thread1 will block again
}
il y a plusieurs problèmes avec ceci:
- Thread2 ne peut pas continuer à "faire le travail qui précède la notification" jusqu'à ce que Thread1 a terminé son travail de "travail à compter de la notification". Avec ce design, Thread2 n'est même pas nécessaire, c'est-à-dire, pourquoi ne pas déplacer "work that precedes" et "work after notification" dans le même thread car un seul peut fonctionner à un moment donné!
- si Thread2 n'est pas capable de prévenir Thread1, Thread1 va immédiatement verrouiller le mutex quand il répète la boucle while(1) et Thread1 va faire le "travail après notification" même s'il n'y avait pas de notification. Cela signifie que vous devez garantir que Thread2 verrouillera le mutex avant Thread1. Comment faites-vous cela? Peut-être forcer un événement d'horaire en dormant ou par d'autres moyens spécifiques à L'OS, mais même cela n'est pas garanti pour fonctionner en fonction du timing, de votre OS, et de l'algorithme d'ordonnancement.
ces deux problèmes ne sont pas mineurs, en fait, ce sont à la fois des défauts de conception majeurs et des bogues latents. L'origine de ces deux problèmes est l'exigence qu'un mutex est verrouillé et déverrouillé dans le même thread. Alors comment faire pour éviter les problèmes ci-dessus? Utilisez les variables de condition!
BTW, si vos besoins de synchronisation sont vraiment simples, vous pouvez utiliser un vieux sémaphore simple qui évite la complexité supplémentaire des variables de condition.
Mutex est pour l'accès exclusif de la ressource partagée, tandis que la variable conditionnelle est pour l'attente d'une condition pour être vrai. Les gens peuvent penser qu'ils peuvent implémenter la variable conditionnelle sans le support du noyau. Une solution commune que l'on pourrait trouver est le "drapeau + mutex" est comme:
lock(mutex)
while (!flag) {
sleep(100);
}
unlock(mutex)
do_something_on_flag_set();
mais cela ne marchera jamais, parce que vous ne relâchez jamais le mutex pendant l'attente, personne d'autre ne peut placer le drapeau d'une manière sûre. C'est pourquoi nous avons besoin d' variable conditionnelle, lorsque vous attendez sur une variable de condition, le mutex associé n'est pas tenu par votre thread jusqu'à ce qu'il soit signalé.
je pensais à cela aussi et l'information la plus importante que je manquais partout était que mutex peut posséder (et changer) à l'époque un seul fil. Donc, si vous avez un producteur et plus de consommateurs, le producteur devra attendre mutex pour produire. Avec cond. variable qu'il peut produire à tout moment.
le Var conditionnel et la paire de mutex peuvent être remplacés par un sémaphore binaire et une paire de mutex. La séquence des opérations d'un fil de consommation lors de l'utilisation du Var conditionnel + mutex est:
-
Verrouiller le mutex
-
attendez sur la var conditionnelle
-
Processus
-
déverrouiller le mutex
la séquence des opérations de filetage du producteur est
-
Verrouiller le mutex
-
Signal La var conditionnelle
-
déverrouiller le mutex
la séquence de fils de consommation correspondante lorsqu'on utilise la paire sema+mutex est
-
Attendre le sema binaire
-
Verrouiller le mutex
-
vérifier l'état prévu
-
si la condition est vraie, traiter.
-
déverrouiller le mutex
-
si le contrôle de l'État à l'étape 3 était faux, retourner à l'étape 1.
La séquence pour le fil de producteur est la suivante:
-
Verrouiller le mutex
-
Afficher le binaire sema
-
déverrouiller le mutex
comme vous pouvez le voir le traitement inconditionnel dans l'étape 3 lors de l'utilisation de la var conditionnelle est remplacé par le traitement conditionnel dans l'étape 3 et l'étape 4 lors de l'utilisation du sema binaire.
la raison est que lors de l'utilisation de sema+mutex, dans un État de course, un autre fil de consommation peut se faufiler entre l'étape 1 et 2 et traiter/annuler la condition. Cela n'arrivera pas avec var conditionnelle. Lors de l'utilisation du var conditionnel, la condition est garantie pour être vraie après l'étape 2.
Le sémaphore binaire peut être remplacé par le comptage régulier de sémaphore. Cela peut conduire à l'étape 6 à l'étape 1 boucle quelques fois de plus.
vous avez besoin de variables de condition, à utiliser avec un mutex (chaque cond.var. appartient à un mutex) pour signaler le changement d'état (conditions) d'un thread à un autre. L'idée est qu'un fil peut attendre jusqu'à ce qu'une condition devienne vraie. Ces conditions sont spécifiques au programme (par exemple "la file d'attente est vide", "la matrice est grande", "une ressource est presque épuisée", "une étape de calcul est terminée", etc.). Un mutex peut avoir plusieurs variables de condition liées. Et vous avez besoin de variables de condition parce que de telles conditions ne peuvent pas toujours être exprimées aussi simplement que "un mutex est verrouillé" (donc vous devez diffuser les changements de conditions à d'autres threads).
lisez quelques bons tutoriels de fil posix, par exemple ce tutoriel ou que ou que . Encore mieux, lisez un bon livre de pthread. Voir cette question .
lire aussi programmation Unix Avancée et programmation Linux Avancée
P. le parallélisme et les fils sont des concepts difficiles à saisir. Prenez le temps de lire, d'expérimenter et de relire.
je pense que c'est une implémentation définie.
Le mutex est suffisant ou non dépend de si vous considérez le mutex comme un mécanisme pour les sections critiques ou quelque chose de plus.
comme indiqué dans http://en.cppreference.com/w/cpp/thread/mutex/unlock ,
le mutex doit être verrouillé par le fil courant d'exécution, sinon, le comportement n'est pas défini.
ce qui signifie qu'un thread ne pouvait déverrouiller qu'un mutex qui était verrouillé/détenu par lui-même en C++.
Mais dans d'autres langages de programmation, vous pourriez être en mesure de partager un mutex entre les processus.
ainsi, distinguer les deux concepts peut être simplement des considérations de rendement, une identification complexe de la propriété ou le partage entre les processus ne sont pas dignes pour des applications simples.
par exemple, vous pouvez fixer le cas de @ slowjelj avec un mutex supplémentaire (il pourrait s'agir d'une correction incorrecte):
Thread1:
lock(mutex0);
while(1) {
lock(mutex0); // Blocks waiting for notification from Thread2
... // do work after notification is received
unlock(mutex1); // Tells Thread2 we are done
}
Thread2:
while(1) {
lock(mutex1); // lock the mutex so Thread1 will block again
... // do the work that precedes notification
unlock(mutex0); // unblocks Thread1
}
mais votre programme se plaindra que vous avez déclenché une assertion laissée par le compilateur (par exemple" déverrouiller un mutex non-propriétaire " dans Visual Studio 2015).