J'ai fait les cours et le support CLR pour le threading dans DotGNU et j'ai quelques réflexions ...
Sauf si vous avez besoin de verrous de processus croisés, vous devez toujours éviter d'utiliser Mutex et sémaphores. Ces classes dans .NET sont des enveloppes autour du mutex et des sémaphores Win32 et sont plutôt lourdes (elles nécessitent un changement de contexte dans le noyau, ce qui coûte cher - surtout si votre verrou n'est pas en conflit).
Comme d'autres sont mentionnés, l'instruction C # lock est la magie du compilateur pour Monitor.Enter et Monitor.Exit (existant dans un try / finally).
Les moniteurs ont un mécanisme de signal / attente simple mais puissant que les Mutex n'ont pas via les méthodes Monitor.Pulse / Monitor.Wait. L'équivalent Win32 serait des objets événement via CreateEvent qui existent également dans .NET en tant que WaitHandles. Le modèle Pulse / Wait est similaire à pthread_signal et pthread_wait d'Unix mais est plus rapide car il peut s'agir d'opérations entièrement en mode utilisateur dans le cas non contesté.
Monitor.Pulse / Wait est simple à utiliser. Dans un thread, nous verrouillons un objet, vérifions un drapeau / état / propriété et si ce n'est pas ce que nous attendons, appelons Monitor.Wait qui relâchera le verrou et attendra qu'une impulsion soit envoyée. Lorsque l'attente revient, nous bouclons et vérifions à nouveau l'indicateur / l'état / la propriété. Dans l'autre thread, nous verrouillons l'objet chaque fois que nous modifions l'indicateur / état / propriété, puis appelons PulseAll pour réveiller les threads d'écoute.
Souvent, nous voulons que nos classes soient thread-safe, donc nous mettons des verrous dans notre code. Cependant, il arrive souvent que notre classe ne soit utilisée que par un seul thread. Cela signifie que les verrous ralentissent inutilement notre code ... c'est là que des optimisations intelligentes du CLR peuvent aider à améliorer les performances.
Je ne suis pas sûr de l'implémentation des verrous par Microsoft, mais dans DotGNU et Mono, un indicateur d'état de verrouillage est stocké dans l'en-tête de chaque objet. Chaque objet dans .NET (et Java) peut devenir un verrou, chaque objet doit donc le prendre en charge dans son en-tête. Dans l'implémentation DotGNU, il existe un indicateur qui vous permet d'utiliser une table de hachage globale pour chaque objet utilisé comme verrou - cela a l'avantage d'éliminer une surcharge de 4 octets pour chaque objet. Ce n'est pas génial pour la mémoire (en particulier pour les systèmes embarqués qui ne sont pas fortement threadés) mais a un impact négatif sur les performances.
Mono et DotGNU utilisent efficacement des mutex pour effectuer le verrouillage / attente, mais utilisent des opérations de comparaison et d'échange de style spinlock pour éliminer le besoin d'effectuer réellement des verrous durs sauf si vraiment nécessaire:
Vous pouvez voir un exemple de la façon dont les moniteurs peuvent être mis en œuvre ici:
http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup