POSIX permet aux mutex d'être récursifs. Cela signifie que le même thread peut verrouiller le même mutex deux fois et ne sera pas bloqué. Bien sûr, il doit également le déverrouiller deux fois, sinon aucun autre thread ne peut obtenir le mutex. Tous les systèmes prenant en charge les pthreads ne prennent pas également en charge les mutex récursifs, mais s'ils veulent être conformes à POSIX, ils doivent le faire .
D'autres API (API de plus haut niveau) proposent également généralement des mutex, souvent appelés Locks. Certains systèmes / langages (par exemple Cocoa Objective-C) offrent à la fois des mutex récursifs et non récursifs. Certaines langues n'offrent également 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écursifs pourrait ne pas poser de problème, car ils peuvent facilement être écrits vous-même (j'ai déjà implémenté des mutex récursifs moi-même sur la base d'opérations mutex / condition plus simples).
Ce que je ne comprends pas vraiment: à quoi servent les mutex non récursifs? Pourquoi voudrais-je avoir un blocage de thread s'il verrouille deux fois le même mutex? Même les langages de haut niveau qui pourraient éviter cela (par exemple, tester si cela va bloquer et lever une exception si c'est le cas) ne le font généralement pas. Ils laisseront le thread bloqué à la place.
Est-ce uniquement pour les cas où je le verrouille accidentellement deux fois et ne le déverrouille qu'une seule fois et dans le cas d'un mutex récursif, il serait plus difficile de trouver le problème, donc à la place, je le verrouille immédiatement pour voir où le verrou incorrect apparaît? Mais ne pourrais-je pas faire la même chose avec le retour d'un compteur de verrouillage lors du déverrouillage et dans une situation où je suis sûr que j'ai relâché le dernier verrou et que le compteur n'est pas nul, je peux lever 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 des performances, car un mutex non récursif peut être légèrement plus rapide qu'un mutex récursif? Cependant, j'ai testé cela et la différence n'est vraiment pas si grande.
La réponse n'est pas l' efficacité. Les mutex non réentrants conduisent à un meilleur code.
Exemple: A :: foo () acquiert le verrou. Il appelle ensuite B :: bar (). Cela a bien fonctionné lorsque vous l'avez écrit. Mais quelque temps plus tard, quelqu'un change B :: bar () pour appeler A :: baz (), qui acquiert également le verrou.
Eh bien, si vous n'avez pas de mutex récursifs, cette impasse. Si vous en avez, il fonctionne, mais il peut se casser. A :: foo () peut avoir laissé l'objet dans un état incohérent avant d'appeler bar (), en supposant que baz () ne peut pas être exécuté car il acquiert également le mutex. Mais il ne devrait probablement pas fonctionner! La personne qui a écrit A :: foo () a supposé que personne ne pouvait appeler A :: baz () en même temps - c'est la raison pour laquelle ces deux méthodes ont acquis le verrou.
Le bon modèle mental pour l'utilisation des mutex: Le mutex protège un invariant. Lorsque le mutex est maintenu, l'invariant peut changer, mais avant de libérer le mutex, l'invariant est rétabli. Les verrous réentrants sont dangereux car la deuxième fois que vous acquérez le verrou, vous ne pouvez plus être sûr que l'invariant est vrai.
Si vous êtes satisfait des verrous réentrants, c'est uniquement parce que vous n'avez pas eu à déboguer un problème comme celui-ci auparavant. Java a des verrous non réentrants ces jours-ci dans java.util.concurrent.locks, d'ailleurs.
la source
Semaphore
.A::foo()
peut avoir laissé l'objet dans un état incohérent avant l'appelA::bar()
. Qu'est-ce que le mutex, récursif ou non, a quelque chose à voir avec ce cas?Comme l'écrit Dave Butenhof lui - même :
"Le plus gros de tous les gros problèmes avec les mutex récursifs est qu'ils vous encouragent à perdre complètement votre schéma de verrouillage et votre portée. C'est mortel. Mal. C'est le" mangeur de fil ". Vous maintenez les verrous pour le temps le plus court possible. Période. Toujours. Si vous appelez quelque chose avec un cadenas maintenu simplement parce que vous ne savez pas qu'il est tenu, ou parce que vous ne savez pas si l'appelé a besoin du mutex, alors vous le tenez trop longtemps. Vous êtes en visant votre application avec un fusil à pompe et en appuyant sur la gâchette. Vous avez probablement commencé à utiliser des threads pour obtenir la concurrence, mais vous venez de PRÉVENIR la concurrence. "
la source
...you're not DONE until they're [recursive mutex] all gone.. Or sit back and let someone else do the design.
Pourquoi êtes-vous sûr que c'est vraiment le 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 à un seul thread et n'a rien de commun avec le multi-threading et les mutex.
De plus, si vous devez protéger les invariants, vous pouvez toujours utiliser un sémaphore binaire qui n'est jamais récursif.
la source
Une des principales raisons pour lesquelles les mutex récursifs sont utiles est en cas d'accès aux méthodes plusieurs fois par le même thread. Par exemple, disons que si le verrouillage mutex protège une banque A / C pour se retirer, alors s'il y a des frais également associés à ce retrait, alors le même mutex doit être utilisé.
la source
Le seul bon cas d'utilisation du mutex de récursivité est lorsqu'un objet contient plusieurs méthodes. Lorsque l'une des méthodes modifie le contenu de l'objet et doit donc verrouiller l'objet avant que l'état ne soit à nouveau cohérent.
Si les méthodes utilisent d'autres méthodes (par exemple: addNewArray () appelle addNewPoint () et se termine avec recheckBounds ()), mais que l'une de ces fonctions par elle-même doit verrouiller le mutex, alors le mutex récursif est gagnant-gagnant.
Pour tout autre cas (résoudre juste un mauvais codage, l'utiliser même dans différents objets) est clairement faux!
la source
Ils sont absolument bons lorsque vous devez vous assurer que le mutex est déverrouillé avant de faire quelque chose. C'est parce que
pthread_mutex_unlock
peut garantir que le mutex est déverrouillé uniquement s'il est non récursif.Si
g_mutex
n'est pas récursif, le code ci-dessus est garanti pour appelerbar()
avec le mutex déverrouillé .Éliminant ainsi la possibilité d'une impasse en cas
bar()
il se trouverait une fonction externe inconnue qui pourrait bien faire quelque chose qui pourrait amener un autre thread à essayer d'acquérir le même mutex. De tels scénarios ne sont pas rares dans les applications construites sur des pools de threads et dans les applications distribuées, où un appel interprocessus peut engendrer un nouveau thread sans que le programmeur client ne s'en rende compte. Dans tous ces scénarios, il est préférable d'appeler lesdites fonctions externes uniquement après le déverrouillage.Si
g_mutex
c'était récursif, il n'y aurait tout simplement aucun moyen de s'assurer qu'il est déverrouillé avant de passer un appel.la source