Quelles sont les différences entre les différentes options de synchronisation des threads dans C #?

Quelqu’un peut-il expliquer la différence entre:

  • verrouiller (un object) {}
  • Utiliser Mutex
  • Utiliser le sémaphore
  • Utilisation du moniteur
  • Utilisation d’autres classes de synchronisation .Net

Je ne peux pas le comprendre. Il me semble que les deux premiers sont les mêmes?

Bonne question Je me trompe peut-être .. Laissez-moi essayer .. Révision # 2 de ma réponse orig .. avec un peu plus de compréhension. Merci de me faire lire 🙂

serrure (obj)

  • est une construction CLR qui pour la synchronisation (intra-object?) du thread. S’assure qu’un seul thread peut s’approprier le verrou de l’object et entrer dans le bloc de code verrouillé. Les autres threads doivent attendre que le propriétaire actuel abandonne le verrou en quittant le bloc de code. Il est également recommandé de verrouiller un object membre privé de votre classe.

Moniteurs

  • lock (obj) est implémenté en interne à l’aide d’un moniteur. Vous devriez préférer lock (obj) car cela vous évite de faire comme si vous oubliez la procédure de nettoyage. C’est la construction du moniteur si vous voulez.
    L’utilisation de Monitor est généralement préférable à celle des mutex, car les moniteurs ont été conçus spécifiquement pour .NET Framework et font donc un meilleur usage des ressources.

L’utilisation d’un verrou ou d’un moniteur est utile pour empêcher l’exécution simultanée de blocs de code sensibles au thread, mais ces constructions ne permettent pas à un thread de communiquer un événement à un autre. Cela nécessite des événements de synchronisation , qui sont des objects dont l’un des deux états, signalé et non signalé, peut être utilisé pour activer et suspendre les threads. Mutex, les sémaphores sont des concepts de niveau OS. Par exemple, avec un mutex nommé, vous pouvez synchroniser plusieurs exe (gérés) (en vous assurant qu’une seule instance de votre application s’exécute sur la machine).

Mutex:

  • Contrairement aux moniteurs, un mutex peut être utilisé pour synchroniser les threads entre les processus. Lorsqu’il est utilisé pour la synchronisation inter-processus, un mutex est appelé un mutex nommé car il doit être utilisé dans une autre application et ne peut donc pas être partagé au moyen d’une variable globale ou statique. Il faut lui donner un nom pour que les deux applications puissent accéder au même object mutex. En revanche, la classe Mutex est un wrapper à une construction Win32. Bien qu’il soit plus puissant qu’un moniteur, un mutex nécessite des transitions interopérables plus onéreuses que celles requirejses par la classe Monitor.

Sémaphores (blessé au cerveau).

  • Utilisez la classe Semaphore pour contrôler l’access à un pool de ressources. Les threads entrent dans le sémaphore en appelant la méthode WaitOne, héritée de la classe WaitHandle, et libèrent le sémaphore en appelant la méthode Release. Le compte sur un sémaphore est décrémenté chaque fois qu’un thread entre dans le sémaphore, et incrémenté lorsqu’un thread libère le sémaphore. Lorsque le nombre est égal à zéro, les requêtes suivantes sont bloquées jusqu’à ce que d’autres threads libèrent le sémaphore. Lorsque tous les threads ont libéré le sémaphore, le nombre correspond à la valeur maximale spécifiée lors de la création du sémaphore. Un thread peut entrer plusieurs fois dans le sémaphore. La classe Sémaphore n’impose pas l’identité des threads sur WaitOne ou Release. Les sémaphores sont de deux types: les sémaphores locaux et les sémaphores nommés . Si vous créez un object Sémaphore à l’aide d’un constructeur qui accepte un nom, il est associé à un sémaphore de système d’exploitation de ce nom. Les sémaphores nommés du système sont visibles dans tout le système d’exploitation et peuvent être utilisés pour synchroniser les activités des processus. Un sémaphore local n’existe que dans votre processus. Il peut être utilisé par n’importe quel thread de votre processus qui a une référence à l’object Semaphore local. Chaque object sémaphore est un sémaphore local distinct.

LA PAGE À LIRE – Synchronisation des threads (C #)

Re “Utilisation d’autres classes de synchronisation .Net” – certaines des autres que vous devez connaître:

  • ReaderWriterLock – permet plusieurs lecteurs ou un seul écrivain (pas en même temps)
  • ReaderWriterLockSlim – comme ci-dessus, frais généraux inférieurs
  • ManualResetEvent – une porte qui permet le code passé quand ouvert
  • AutoResetEvent – comme ci-dessus, mais se ferme automatiquement une fois ouvert

Il y a aussi plus de constructions de locking (faible surcharge) dans CCR / TPL (les CTP Parallel Extensions ) – mais IIRC, elles seront disponibles dans .NET 4.0

Comme indiqué dans ECMA, et comme vous pouvez l’observer à partir des méthodes Reflected, l’instruction de locking est fondamentalement équivalente à

 object obj = x; System.Threading.Monitor.Enter(obj); try { … } finally { System.Threading.Monitor.Exit(obj); } 

L’exemple ci-dessus montre que les moniteurs peuvent verrouiller des objects.

Mutexe sont utiles lorsque vous avez besoin d’une synchronisation interprocess car ils peuvent verrouiller un identificateur de chaîne. Le même identificateur de chaîne peut être utilisé par différents processus pour acquérir le verrou.

Les sémaphores sont comme les mutex sur les stéroïdes, ils permettent un access simultané en fournissant un nombre maximum d’access simultanés ». Une fois la limite atteinte, le sémaphore commence à bloquer tout access supplémentaire à la ressource jusqu’à ce que l’un des appelants libère le sémaphore.

J’ai fait les classes et le support CLR pour le threading dans DotGNU et j’ai quelques reflections …

Sauf si vous avez besoin de verrous inter-processus, vous devriez toujours éviter d’utiliser Mutex & Semaphores. Ces classes dans .NET sont des enveloppes autour de Win32 Mutex et des sémaphores et sont plutôt lourdes (elles nécessitent un changement de contexte dans le kernel, ce qui est coûteux – surtout si votre verrou n’est pas en conflit).

Comme d’autres sont mentionnés, l’instruction de locking C # 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 objects é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 en mode utilisateur dans le cas non contesté.

Monitor.Pulse / Wait est simple à utiliser. Dans un thread, nous verrouillons un object, vérifions un drapeau / state / property et si ce n’est pas ce que nous attendons, appelons Monitor.Wait qui libère le verrou et attend qu’une impulsion soit envoyée. Lorsque l’attente retourne, nous effectuons une boucle et vérifions à nouveau la propriété flag / state /. Dans l’autre thread, nous verrouillons l’object chaque fois que nous modifions la propriété flag / state /, puis appelons PulseAll pour réveiller les threads d’écoute.

Souvent, nous voulons que nos classes soient sûres pour les threads, 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 dans le 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 locking est stocké dans l’en-tête de chaque object. Chaque object en .NET (et Java) peut devenir un verrou, de sorte que chaque object doit le supporter dans son en-tête. Dans l’implémentation DotGNU, il y a un indicateur qui vous permet d’utiliser une table de hachage globale pour chaque object utilisé comme verrou, ce qui a l’avantage d’éliminer une surcharge de 4 octets pour chaque object. Ce n’est pas génial pour la mémoire (en particulier pour les systèmes embarqués qui ne sont pas très threadés) mais a un impact sur les performances.

Mono et DotGNU utilisent tous deux des mutex pour effectuer des opérations de locking / attente, mais utilisent des opérations de comparaison et d’échange de style spinlock pour éliminer le besoin de réellement effectuer des verrous, sauf si cela est vraiment nécessaire:

Vous pouvez voir un exemple de la façon dont les moniteurs peuvent être implémentés ici:

http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup

Une mise en garde supplémentaire pour le locking sur un Mutex partagé que vous avez identifié avec un identifiant de chaîne est qu’il sera par défaut un mutex “Local \” et ne sera pas partagé entre les sessions dans un environnement de serveur Terminal Server.

Préfixez votre identifiant de chaîne avec “Global \” pour vous assurer que l’access aux ressources système partagées est correctement contrôlé. Je rencontrais tout un tas de problèmes lors de la synchronisation des communications avec un service exécuté sous le compte SYSTEM avant de m’en rendre compte.

Je voudrais essayer d’éviter “lock ()”, “Mutex” et “Monitor” si vous le pouvez …

Découvrez le nouvel espace de noms System.Collections.Concurrent dans .NET 4
Il possède de belles classes de collecte sécurisées pour les threads

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

ConcurrentDictionary roches! plus de locking manuel pour moi!