Les sémaphores empêchent-ils la réorganisation des instructions?

Je cherchais un équivalent attendu des déclarations de locking en C #. Certaines personnes suggèrent d’utiliser un SemaphoreSlim binary de la manière suivante:

 await semaphore.WaitAsync().ConfigureAwait(false); try { //inner instructions } finally { semaphore.Release(); } 

Je sais que cela pose certains problèmes (par exemple, ce n’est pas réentrant), mais ce qui me préoccupe le plus est avec la réorganisation des instructions.

Dans les anciennes déclarations de locking simples, nous avons la garantie qu’aucune instruction interne de la serrure ne sera déplacée à l’extérieur (avant ou après) de la déclaration de locking. Est-ce la même chose pour cette solution de sémaphore? Autant que je sache, la documentation ne mentionne pas ce problème.

SemaphoreSlim , et à peu près toutes les autres constructions de synchronisation, sont construits en utilisant un Monitor (ou d’autres types construits sur un Monitor ) en interne, ce qui correspond exactement à la manière dont un lock est implémenté, vous offrant les mêmes garanties.

La garantie SemaphoreSlim est un peu implicite. C’est décrit comme une primitive de synchronisation de locking dans Présentation des primitives de synchronisation .

Je ne suis pas un expert en modèles de mémoire, mais maintenant je pense que nous avons ces garanties.

Comme Servy l’a souligné , les méthodes Wait et Release utilisent un Monitor sous le capot. Cependant, un Monitor à lui seul peut ne pas suffire .

À la fin de la méthode Wait , juste avant l’appel Monitor.Exit , un champ volatile est décrémenté.

 if (lockTaken) { m_waitCount--; //m_waitCount is volatile Monitor.Exit(m_lockObj); } 

Autant que je sache, l’opérateur de décrémentation utilisé sur un champ instable introduit à la fois les opérations “acquérir” et “relâcher”, empêchant ainsi la réorganisation des instructions suivantes.

En ce qui concerne la méthode Release , la situation est analogue. Au début, nous avons à la fois l’acquisition du verrou et l’opération lecture-écriture volatile.

 lock (m_lockObj) { //m_currentCount is volatile if (m_maxCount - m_currentCount < releaseCount) { throw new SemaphoreFullException(); } m_currentCount += releaseCount; 

Un merci spécial à Joe Duffy pour avoir souligné l'importance des champs volatiles dans le SemaphoreSlim .

EDIT : exemple montrant une situation dans laquelle les verrous (sans opérations volatiles supplémentaires) peuvent ne pas suffire.

 // Semaphore.Wait() lock (syncRoot) { // (1) // acquire semaphore } // end of Semaphore.Wait() // the critical section guarded by the 'semaphore lock' (2) // Semaphore.Release() lock (syncRoot) { // release semaphore } // end of Semaphore.Release() 

Une instruction de lecture de la section critique (2) peut être réordonnée sur (1) , lorsque le sémaphore n'est pas encore acquis (un autre fil peut toujours fonctionner dans une section critique).