web-dev-qa-db-fra.com

Quelles sont les différences entre les différentes options de synchronisation de threading en C #?

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

  • lock (someobject) {}
  • Utilisation de Mutex
  • Utilisation de Semaphore
  • Utilisation du moniteur
  • Utilisation d'autres classes de synchronisation .Net

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

158
user38834

Grande question. Je me trompe peut-être .. Laissez-moi essayer .. Révision # 2 de ma réponse d'origine .. avec un peu plus de compréhension. Merci de m'avoir fait lire :)

verrouillage (obj)

  • est une construction CLR pour la synchronisation de threads (intra-objet?). Garantit qu'un seul thread peut s'approprier le verrou de l'objet 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 objet 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 il vous empêche de faire des bêtises comme oublier la procédure de nettoyage. C'est une construction idiote à l'épreuve si vous voulez.
    L'utilisation de Monitor est généralement préférée aux mutex, car les moniteurs ont été conçus spécifiquement pour le .NET Framework et utilisent donc mieux les 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 aux threads, 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 objets qui ont l'un des deux états, signalés et non signalés, qui peuvent être utilisés 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 exes (gérés) (en veillant à ce qu'une seule instance de votre application s'exécute sur la machine.)

Mutex:

  • Contrairement aux moniteurs, cependant, un mutex peut être utilisé pour synchroniser les threads entre les processus. Lorsqu'il est utilisé pour la synchronisation interprocessus, un mutex est appelé a nommé mutex 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 objet mutex. En revanche, la classe Mutex est un wrapper pour une construction Win32. Bien qu'il soit plus puissant qu'un moniteur, un mutex nécessite des transitions d'interopérabilité qui sont plus coûteuses en termes de calcul que celles requises par la classe Monitor.

Sémaphores (blessé mon cerveau).

  • Utilisez la classe Semaphore pour contrôler l'accès à 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 nul, les requêtes suivantes se bloquent jusqu'à ce que d'autres threads libèrent le sémaphore. Lorsque tous les threads ont libéré le sémaphore, le nombre est à 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 Semaphore n'applique pas l'identité du thread sur WaitOne ou Release .. la responsabilité des programmeurs de ne pas se foutre en l'air. Sémaphores sont de deux types: les sémaphores locaux et les sémaphores système nommés . Si vous créez un objet Semaphore à l'aide d'un constructeur qui accepte un nom, il est associé à un sémaphore du système d'exploitation de ce nom. Les sémaphores système nommés sont visibles dans tout le système d'exploitation et peuvent être utilisés 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'objet Semaphore local. Chaque objet Semaphore est un sémaphore local distinct.

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

131
Gishu

Re "Utilisation d'autres classes de synchronisation .Net" - certaines des autres que vous devriez connaître:

Il y a aussi plus de constructions de verrouillage (faible surcharge) dans CCR/TPL (les extensions parallèles CTP) - mais IIRC, celles-ci seront rendues disponibles dans .NET 4.0

29
Marc Gravell

Comme indiqué dans ECMA, et comme vous pouvez le constater à partir des méthodes Reflected, l'instruction lock est fondamentalement équivalente à

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

À partir de l'exemple susmentionné, nous voyons que les moniteurs peuvent verrouiller des objets.

Les mutexe sont utiles lorsque vous avez besoin d'une synchronisation interprocessus car ils peuvent se verrouiller sur un identifiant de chaîne. Le même identifiant de chaîne peut être utilisé par différents processus pour acquérir le verrou.

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

14
arul

J'ai fait les classes et le support CLR pour le filetage 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 & Semaphores. Ces classes dans .NET sont des wrappers autour du Mutex Win32 et des sémaphores et sont plutôt lourdes (elles nécessitent un changement de contexte dans le noyau qui est cher - surtout si votre verrou n'est pas en conflit).

Comme d'autres sont mentionnés, l'instruction de verrouillage C # est la magie du compilateur pour Monitor.Enter et Monitor.Exit (existant dans un essai/enfin).

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 d'é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 indicateur/état/propriété et si ce n'est pas ce que nous attendons, appelons Monitor.Wait qui libérera le verrou et attendra jusqu'à ce 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/l'état/la propriété, puis appelons PulseAll pour réveiller les threads d'écoute.

Souvent, nous voulons que nos classes soient thread-safe, nous mettons donc 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 verrouillage est stocké dans l'en-tête de chaque objet. Chaque objet en .NET (et Java) peut devenir un verrou, donc chaque objet doit le prendre en charge 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 objet utilisé comme verrou - cela a l'avantage d'éliminer une surcharge de 4 octets pour chaque objet. Ce n'est pas idéal pour la mémoire (en particulier pour les systèmes embarqués qui ne sont pas lourdement threadés), mais a un impact sur les performances.

Mono et DotGNU utilisent tous les deux des mutex pour effectuer le verrouillage/l'attente, mais utilisent un style spinlock comparer-et-échanger opérations pour éliminer la nécessité d'effectuer réellement un verrouillage dur sauf si 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

13
tumtumtum

Une mise en garde supplémentaire pour le verrouillage sur tout Mutex partagé que vous avez identifié avec un ID 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 de terminaux.

Préfixez votre identifiant de chaîne avec "Global \" pour vous assurer que l'accès aux ressources système partagées est correctement contrôlé. Je rencontrais juste un tas de problèmes de synchronisation des communications avec un service fonctionnant sous le compte SYSTEM avant de m'en rendre compte.

9
nvuono

J'essaierais d'éviter "lock ()", "Mutex" et "Monitor" si vous le pouvez ...

Découvrez le nouvel espace de noms System.Collections.Concurrent dans .NET 4
Il a quelques belles classes de collection thread-safe

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

Roches simultanées! plus de verrouillage manuel pour moi!

5
Peter Gfader