Quand utiliser le mutex récursif?

Je crois comprendre que le mutex récursif permet de verrouiller le mutex plus d’une fois sans se retrouver dans une impasse et qu’il doit être déverrouillé le même nombre de fois. Mais dans quelles situations spécifiques avez-vous besoin d’utiliser un mutex récursif? Je recherche des situations de conception / niveau de code.

Par exemple, lorsque vous avez une fonction qui l’appelle de manière récursive et que vous souhaitez y accéder de manière synchronisée:

void foo() { ... mutex_acquire(); ... foo(); ... mutex_release(); } 

sans un mutex récursif, vous devez d’abord créer une fonction “point d’entrée”, ce qui devient encombrant lorsque vous avez un ensemble de fonctions récursives. Sans mutex récursif:

 void foo_entry() { mutex_acquire(); foo(); mutex_release(); } void foo() { ... foo(); ... } 

Les mutex récursifs et non récursifs ont des cas d’utilisation différents . Aucun type de mutex ne peut facilement remplacer l’autre. Les mutex non récursifs ont moins de surcharge, et les mutex récursifs ont, dans certaines situations, une sémantique utile ou même nécessaire et dans d’autres situations, une sémantique dangereuse ou même cassée. Dans la plupart des cas, quelqu’un peut remplacer n’importe quelle stratégie en utilisant des mutex récursifs avec une stratégie différente plus sûre et plus efficace basée sur l’utilisation de mutex non récursifs.

  • Si vous souhaitez simplement exclure d’autres threads de votre ressource protégée mutex, vous pouvez utiliser n’importe quel type de mutex , mais vous pouvez vouloir utiliser le mutex non récursif en raison de sa surcharge réduite.
  • Si vous voulez appeler des fonctions récursivement, qui bloquent le même mutex, alors elles
    • utiliser un mutex récursif , ou
    • déverrouiller et verrouiller le même mutex non récursif encore et encore (méfiez-vous des threads simultanés!) (en supposant que cela soit sémantiquement valable, cela pourrait toujours poser problème), ou
    • doivent en quelque sorte annoter les mutex déjà bloqués (en simulant une propriété / mutex récursive).
  • Si vous souhaitez verrouiller plusieurs objects protégés contre les mutex à partir d’un ensemble de ces objects, où les ensembles pourraient avoir été créés en fusionnant, vous pouvez choisir
    • utiliser par object exactement un mutex , permettant à plus de threads de travailler en parallèle, ou
    • utiliser par object une référence à un mutex récursif éventuellement partagé , afin de réduire la probabilité de ne pas verrouiller tous les mutex ensemble, ou
    • utiliser par object une référence comparable à un mutex non récursif éventuellement partagé , en contournant l’intention de verrouiller plusieurs fois.
  • Si vous souhaitez libérer un verrou dans un thread différent de celui qui a été verrouillé, vous devez utiliser des verrous non récursifs (ou des verrous récursifs qui autorisent explicitement cela au lieu de générer des exceptions).
  • Si vous souhaitez utiliser des variables de synchronisation , vous devez pouvoir déverrouiller explicitement le mutex en attendant une variable de synchronisation, afin que la ressource soit autorisée à être utilisée dans d’autres threads. Cela est seulement possible avec des mutex non récursifs , car l’appelant de la fonction en cours peut déjà verrouiller des mutex récursifs.

J’ai rencontré le besoin d’un mutex récursif aujourd’hui, et je pense que c’est peut-être l’exemple le plus simple parmi les réponses publiées jusqu’à présent: il s’agit d’une classe qui expose deux fonctions API, Processus (…) et reset ().

 public void Process(...) { acquire_mutex(mMutex); // Heavy processing ... reset(); ... release_mutex(mMutex); } public void reset() { acquire_mutex(mMutex); // Reset ... release_mutex(mMutex); } 

Les deux fonctions ne doivent pas s’exécuter simultanément car elles modifient les composants internes de la classe. Je souhaitais donc utiliser un mutex. Le problème est que Process () appelle reset () en interne, ce qui créerait un blocage car mMutex est déjà acquis. Les verrouiller avec un verrou récursif corrige plutôt le problème.

Si vous voulez voir un exemple de code utilisant des mutex récursifs, consultez les sources de “Elecsortingc Fence” pour Linux / Unix. «C’était l’un des outils Unix courants pour trouver des« vérifications de limites »en lecture / écriture et en sous-utilisation, ainsi que l’utilisation de la mémoire libérée avant l’arrivée de Valgrind .

Comstackz et liez simplement la clôture élecsortingque avec les sources (option -g avec gcc / g ++), puis associez-la à votre logiciel avec l’option de lien -lefence et commencez à parcourir les appels à malloc / free. http://elinux.org/Elecsortingc_Fence

Ce serait certainement un problème si un thread bloquait la tentative d’acquérir (encore) un mutex qu’il possédait déjà …

Y a-t-il une raison de ne pas permettre à un mutex d’être acquis plusieurs fois par le même thread?