175 votes

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

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

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

Je n'arrive pas à comprendre. Il me semble que les deux premiers sont les mêmes ?

0 votes

Ce lien m'a beaucoup aidé : albahari.com/filetage

142voto

Gishu Points 59012

Excellente question. J'ai peut-être tort Laissez-moi essayer Révision #2 de ma réponse originale avec un peu plus de compréhension. Merci de m'avoir fait lire :)

verrouiller(obj)

  • est une construction CLR qui permet la synchronisation des threads (intra-objet ?). Elle garantit qu'un seul thread peut prendre possession du verrou de l'objet et entrer dans le bloc de code verrouillé. Les autres threads doivent attendre que le propriétaire actuel renonce au 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 en utilisant un Moniteur. Vous devriez préférer lock(obj) parce qu'il vous empêche de faire une gaffe comme oublier la procédure de nettoyage. Il protège la construction du moniteur contre les erreurs, si vous voulez.
    L'utilisation de Monitor est généralement préférée aux mutex, car les monitors ont été conçus spécifiquement pour le .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 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 Les sémaphores, qui sont des objets ayant un des deux états, signalé et non signalé, peuvent être utilisés pour activer et suspendre les threads. Les mutex et les sémaphores sont des concepts de niveau OS. Par exemple, avec un mutex nommé, vous pouvez synchroniser plusieurs exes (gérés) (en vous assurant qu'une seule instance de votre application tourne 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 inter-processus, un mutex est appelé un mutex nommé parce qu'il est destiné à être utilisé dans une autre application, et qu'il 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 une enveloppe pour une construction Win32. Bien qu'elle soit plus puissante qu'un moniteur, une mutex nécessite des transitions interop qui sont plus coûteuses en termes de calcul que celles requises par la classe Monitor.

Sémaphores (j'ai mal au cerveau).

  • Utilisez la classe Sémaphore pour contrôler l'accès à un pool de ressources. Les threads entrent dans le sémaphore en appelant la méthode WaitOne, qui est héritée de la classe WaitHandle, et libèrent le sémaphore en appelant la méthode Release. Le compte d'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 compte est égal à zéro, les demandes 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 compte est à la valeur maximale spécifiée lors de la création du sémaphore. Un thread peut entrer dans le sémaphore plusieurs fois La classe Sémaphore n'impose pas l'identité du thread sur WaitOne ou Release Il est de la responsabilité des programmeurs de ne pas faire d'erreur. Les sémaphores sont de deux types : les sémaphores locaux et les sémaphores nommés. sémaphores du système. Si vous créez un objet Sémaphore en utilisant un constructeur qui accepte un nom, il est associé à un sémaphore du système d'exploitation de ce nom. Les sémaphores nommés 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 tout thread de votre processus qui possède une référence à l'objet Sémaphore local. Chaque objet Sémaphore est un sémaphore local distinct.

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

19 votes

Vous prétendez que Monitor ne permet pas la communication est incorrecte ; vous pouvez toujours Pulse etc. avec un Monitor

4 votes

Consultez une description alternative des sémaphores - stackoverflow.com/a/40473/968003 . Imaginez les sémaphores comme les videurs d'une boîte de nuit. Un certain nombre de personnes sont autorisées à entrer dans la boîte en même temps. Si la boîte est pleine, personne n'est autorisé à entrer, mais dès qu'une personne part, une autre peut entrer.

31voto

Marc Gravell Points 482669

Re "Using Other .Net synchronization classes"- certaines des autres classes que vous devriez connaître :

Il existe également d'autres constructions de verrouillage (à faible surcharge) dans CCR/TPL (la fonction Extensions parallèles CTP) - mais il semble qu'ils seront disponibles dans .NET 4.0.

0 votes

Donc, si je veux une simple communication par signal (par exemple, l'achèvement d'une opération asynchrone), je devrais utiliser Monitor.Pulse ? ou utiliser SemaphoreSlim ou TaskCompletionSource ?

0 votes

Utiliser TaskCompletionSource pour une opération asynchrone. En fait, il faut arrêter de penser aux threads et commencer à penser aux tâches (unités de travail). Les threads sont un détail d'implémentation et ne sont pas pertinents. En renvoyant un TCS, vous pouvez renvoyer les résultats, les erreurs ou gérer l'annulation et c'est facilement compatible avec d'autres opérations asynchrones (comme async await ou ContinueWith).

15voto

arul Points 10719

Comme indiqué dans l'ECMA, et comme vous pouvez l'observer dans les méthodes réfléchies, la déclaration de verrouillage 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 objets.

Les Mutexe sont utiles lorsque vous avez besoin d'une synchronisation interprocessus car ils peut verrouiller 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 des stéroïdes, ils permettent l'accès simultané en fournissant un compte maximum 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.

5 votes

Ce sucre syntaxique a été légèrement modifié dans le C#4. blogs.msdn.com/ericlippert/archive/2009/03/06/

14voto

tumtumtum Points 520

J'ai fait les classes et le support CLR pour le threading dans DotGNU et j'ai quelques réflexions...

À moins que vous n'ayez besoin de verrouillages interprocessus, vous devriez toujours éviter d'utiliser les Mutex et les sémaphores. Ces classes dans .NET sont des enveloppes autour des Mutex et Sémaphores Win32 et sont plutôt lourdes (elles nécessitent un changement de contexte dans le noyau, ce qui est coûteux - surtout si votre verrou n'est pas en conflit).

Comme d'autres l'ont mentionné, l'instruction de verrouillage C# est une 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 d'événement via CreateEvent qui existent également dans .NET sous la forme de WaitHandles. Le modèle Pulse/Wait est similaire au pthread_signal et au pthread_wait d'Unix, mais il est plus rapide parce qu'il peut s'agir d'opérations entièrement en mode utilisateur dans les cas non-contrôlés.

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, nous appelons Monitor.Wait qui va libérer le verrou et attendre qu'une impulsion soit envoyée. Lorsque l'attente revient, nous bouclons et vérifions à nouveau le drapeau/état/propriété. Dans l'autre thread, nous verrouillons l'objet chaque fois que nous changeons le drapeau/état/propriété, puis nous appelons PulseAll pour réveiller tous les threads qui écoutent.

Souvent, nous voulons que nos classes soient thread safe, alors nous mettons des verrous dans notre code. Cependant, il est fréquent 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 drapeau d'état de verrouillage est stocké dans l'en-tête de chaque objet. Chaque objet dans .NET (et Java) peut devenir un verrou, donc chaque objet doit le supporter dans son en-tête. Dans l'implémentation de DotGNU, il y a un drapeau qui vous permet d'utiliser une table de hachage globale pour chaque objet qui est utilisé comme un verrou -- cela a l'avantage d'éliminer un overhead de 4 octets pour chaque objet. Ce n'est pas génial pour la mémoire (surtout pour les systèmes embarqués qui ne sont pas fortement threadés) mais cela a un impact sur les performances.

Mono et DotGNU utilisent effectivement les mutex pour effectuer le verrouillage et l'attente mais utilisent un style de spinlock. comparer et échanger afin d'éliminer la nécessité d'effectuer des verrouillages forcés, sauf si cela est vraiment nécessaire :

Vous pouvez voir ici un exemple de la façon dont les moniteurs peuvent être mis en œuvre :

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

9voto

nvuono Points 1731

Une mise en garde supplémentaire concernant le verrouillage d'un Mutex partagé que vous avez identifié avec un ID de chaîne est qu'il s'agira par défaut d'un Mutex " local " et qu'il ne sera pas partagé entre les sessions dans un environnement de serveur terminal.

Faites précéder votre identifiant de chaîne de caractères de "Global\" pour vous assurer que l'accès aux ressources partagées du système est correctement contrôlé. J'étais en train de rencontrer tout un tas de problèmes pour synchroniser les communications avec un service fonctionnant sous le compte SYSTEM avant de m'en rendre compte.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X