Verrouillage récursif (Mutex) vs verrouillage Non récursif (Mutex))
POSIX permet aux mutex d'être récursifs. Cela signifie que le même fil peut verrouiller le même mutex deux fois et ne sera pas bloqué. Bien sûr, il doit aussi le déverrouiller deux fois, sinon aucun autre fil ne peut obtenir le mutex. Tous les systèmes supportant des pthreads ne supportent pas aussi des Mutex récursifs, mais s'ils veulent être conformes POSIX, ils doivent être conformes .
D'autres API (APIs de niveau plus élevé) offrent aussi généralement des Mutex, souvent appelés serrures. Quelque les systèmes / langues (par exemple objectif cacao-C) offrent à la fois des Mutex récursifs et non récursifs. Certaines langues n'offrent que l'une ou l'autre. Par exemple: en Java, les mutex sont toujours récursifs (le même thread peut se "synchroniser" deux fois sur le même objet). En fonction des autres fonctionnalités de thread qu'ils offrent, ne pas avoir de mutex récursif pourrait ne pas être un problème, car ils peuvent facilement être écrits vous-même (j'ai déjà mis en œuvre des Mutex récursifs moi-même sur la base de mutex/condition plus simple opérations.)
ce que je ne comprends pas vraiment: à quoi servent les Mutex non récursifs? Pourquoi voudrais-je avoir un blocage de fil s'il ferme le même mutex deux fois? Même les langages de haut niveau qui pourraient éviter cela (par exemple, tester si cela va se bloquer et lancer une exception si c'est le cas) ne le font généralement pas. Ils laisseront le fil de blocage à la place.
est ceci seulement pour les cas, où je l'ai accidentellement verrouillé deux fois et déverrouiller seulement une fois et en dans le cas d'un mutex récursif, il serait plus difficile de trouver le problème, donc à la place j'ai immédiatement une impasse pour voir où la mauvaise serrure apparaît? Mais ne pourrais-je pas faire la même chose avec un compteur de verrouillage retourné lors du déverrouillage et dans une situation, où je suis sûr que j'ai libéré la dernière serrure et le compteur n'est pas zéro, je peux jeter une exception ou enregistrer le problème? Ou y a-t-il un autre cas d'utilisation plus utile de mutex non récursifs que je ne vois pas? Ou est-ce peut-être juste de la performance, comme un non-récursive mutex peut être légèrement plus rapide qu'un récursive? Cependant, j'ai testé ceci et la différence n'est vraiment pas si grande.
6 réponses
la différence entre un mutex récursif et non récursif est liée à la propriété. Dans le cas d'un mutex récursif, le noyau doit garder la trace du fil qui a obtenu le mutex la première fois afin qu'il puisse détecter la différence entre la récursion et un fil différent qui devrait se bloquer à la place. Comme une autre réponse l'a souligné, il y a une question de la surcharge de ce à la fois en termes de mémoire pour stocker ce contexte et aussi les cycles requis pour l'entretenir.
cependant , il y a d'autres considérations en jeu ici aussi.
parce que le mutex récursif a un sens de propriété, le fil qui saisit le mutex doit être le même fil qui libère le mutex. Dans le cas des Mutex non récursifs, il n'y a pas de sentiment de propriété et tout fil peut généralement libérer le mutex peu importe quel fil a pris le mutex à l'origine. Dans de nombreux cas, ce type de "mutex" est vraiment plus d'une action de sémaphore, où vous n'êtes pas nécessairement en utilisant le mutex comme un dispositif d'exclusion, mais utilisez-le comme un dispositif de synchronisation ou de signalisation entre deux ou plusieurs threads.
une autre propriété qui vient avec un sentiment de propriété dans un mutex est la capacité de soutenir l'héritage de priorité. Parce que le noyau peut suivre le thread possédant le mutex et aussi l'identité de tous les bloqueurs(s), dans un système fileté de priorité il devient possible d'escalader la priorité du thread qui possède actuellement le mutex à la priorité du thread de priorité plus élevé qui bloque actuellement sur le mutex. Cet héritage prévient le problème d'inversion de priorité qui peut se produire dans de tels cas. (Il est à noter que tous les systèmes ne prennent pas en charge l'héritage de priorité sur ces mutex, mais c'est une autre caractéristique qui devient possible via la notion de propriété).
si vous vous référez au noyau RTOS classique de VxWorks, ils définissent trois mécanismes:
- mutex - prise en charge de la récursion, et éventuellement de l'héritage prioritaire
- sémaphore binaire - pas de récursion, pas d'héritage, exclusion simple, preneur et donneur ne doit pas être le même fil, diffusion disponible
- compte sémaphore - aucune récursion ou héritage, agit comme un compteur de ressources cohérent de n'importe quel compte initial désiré, les threads ne bloquent que lorsque le compte net par rapport à la ressource est zéro.
encore une fois, cela varie quelque peu selon la plate - forme-surtout ce qu'ils appellent ces choses, mais cela devrait être représentatif des concepts et des divers mécanismes en jeu.
la réponse est et non efficacité. Les Mutex Non-rentrants conduisent à un meilleur code.
exemple: A:: foo () acquiert la serrure. Il appelle alors B:: bar (). Ça a bien marché quand tu l'as écrit. Mais plus tard quelqu'un change B::bar() pour appeler A::baz(), qui acquiert aussi la serrure.
Eh bien, si vous n'avez pas de mutex récursifs, ces blocages. Si vous en avez, Elle court, mais elle peut se briser. A::foo() a quitté le objet dans un état incohérent avant d'appeler bar(), sur la supposition que baz() ne pouvait pas être exécuté parce qu'il acquiert également le mutex. Mais il est préférable de ne pas l'exécuter! La personne qui a écrit un::foo() a supposé que personne ne pouvait appeler un::baz() en même temps - c'est la raison pour laquelle ces deux méthodes ont acquis la serrure.
le bon modèle mental pour l'utilisation des Mutex: le mutex protège un invariant. Quand le mutex est tenu, l'invariant peut changer, mais avant libérer le mutex, l'invariant est rétablie. Serrures de rentrée sont dangereux parce que la deuxième fois que vous acquérez la serrure, vous ne pouvez pas être sûr que l'invariant est vrai plus.
Si vous êtes heureux avec réentrant les verrous, c'est seulement parce que vous n'avez pas eu de déboguer un problème comme ça avant. Java a des serrures non-rentrantes ces jours-ci.util.simultané.serrures, par la manière.
comme écrit par Dave Butenhof lui-même :
" le plus gros de tous les gros problèmes avec les Mutex récursifs est que ils vous encouragent à perdre complètement la trace de votre système de verrouillage et portée. C'est mortel. Mal. C'est le "fil eater". Vous tenez les serrures pour absolument plus brefs délais. Période. Toujours. Si vous êtes d'appel quelque chose avec une serrure tenue simplement parce que vous ne savez pas qu'elle est tenue, ou parce que vous ne savez pas si la callee a besoin du mutex, alors tu es le tenir trop longtemps. Vous pointez un fusil de chasse sur votre application et tirer sur la gâchette. Vous avez probablement commencé à utiliser des fils pour obtenir la concurrence; mais vous venez D'empêcher la concurrence."
le bon modèle mental pour utiliser mutex: Le mutex protège invariant.
Pourquoi êtes-vous sûr que c'est vraiment bon modèle mental pour l'utilisation de mutex? Je pense que le bon modèle protège les données mais pas les invariants.
le problème de la protection des invariants se présente même dans les applications à filetage simple et n'a rien de commun avec les multi-filetages et les Mutex.
en outre, si vous besoin de protéger les invariants, vous pouvez toujours utiliser le sémaphore binaire wich n'est jamais récursive.
une raison principale pour laquelle les Mutex récursifs sont utiles est dans le cas d'accéder aux méthodes plusieurs fois par le même thread. Par exemple, si mutex lock protège une banque A/C de retirer, alors s'il ya une taxe également associée à ce retrait, alors le même mutex doit être utilisé.
le seul bon cas d'utilisation pour mutex de récursion est quand un objet contient plusieurs méthodes. Lorsque l'une des méthodes de modifier le contenu de l'objet, et, par conséquent, doit verrouiller l'objet avant de l'état est cohérent.
si les méthodes utilisent d'autres méthodes (c'est-à-dire que addNewArray() appelle addNewPoint(), et se termine par recheckBounds()), mais que l'une de ces fonctions doit elle-même verrouiller le mutex, alors le mutex récursif est gagnant-gagnant.
Pour tout autre cas (résoudre juste un mauvais codage, en l'utilisant même dans des objets différents) est clairement faux!