Vue d’ensemble des primitives de synchronisation
.NET fournit un éventail de types que vous pouvez utiliser pour synchroniser l’accès à une ressource partagée ou coordonner l’interaction des threads.
Important
Utilisez la même instance de primitives de synchronisation pour protéger l’accès à une ressource partagée. Si vous utilisez différentes instances de primitives de synchronisation pour protéger la même ressource, vous contournerez la protection fournie par une primitive de synchronisation.
Classe WaitHandle et types de synchronisation légers
Plusieurs primitives de synchronisation .NET dérivent la classe System.Threading.WaitHandle, qui encapsule un descripteur de synchronisation du système d’exploitation natif et utilise un mécanisme de signalisation pour l’interaction des threads. Ces classes incluent :
- System.Threading.Mutex, qui accorde un accès exclusif à une ressource partagée. L’état d’un mutex est signalé si aucun thread ne le possède.
- System.Threading.Semaphore, qui limite le nombre de threads qui peuvent accéder simultanément à une ressource partagée ou à un pool de ressources. L’état d’un sémaphore est défini sur « signalé » quand son nombre est supérieur à zéro et sur « non signalé » quand son nombre est égal à zéro.
- System.Threading.EventWaitHandle, qui représente un événement de synchronisation de thread et peut être dans un état signalé ou non signalé.
- System.Threading.AutoResetEvent, qui est dérivé de EventWaitHandle et, quand il est signalé, se réinitialise automatiquement à un état non signalé après avoir libéré un seul thread en attente.
- System.Threading.ManualResetEvent, qui est dérivé à son EventWaitHandle et, quand il est signalé, reste dans un état signalé jusqu'à ce que la méthode Reset soit appelée.
Dans .NET Framework, comme WaitHandle est dérivé de System.MarshalByRefObject, ces types peuvent être utilisés pour synchroniser les activités des threads au-delà des limites des domaines d’application.
Dans .NET Framework, .NET Core et .NET 5 (et versions ultérieures), certains de ces types peuvent représenter des descripteurs de synchronisation système nommés, qui sont visibles dans tout le système d’exploitation et peuvent être utilisés pour la synchronisation entre les processus suivants :
- Mutex
- Semaphore (sur Windows)
- EventWaitHandle (sur Windows)
Pour plus d'informations, consultez la référence d’API WaitHandle.
Les types de synchronisation légers ne s’appuient pas sur les descripteurs de système d’exploitation sous-jacents et fournissent généralement de meilleures performances. Toutefois, ils ne peuvent pas être utilisés pour la synchronisation entre processus. Utilisez ces types pour la synchronisation de threads dans une même application.
Certains de ces types sont des alternatives aux types dérivés de WaitHandle. Par exemple, SemaphoreSlim est une alternative légère à Semaphore.
Synchronisation de l'accès à une ressource partagée
.NET fournit une plage de primitives de synchronisation pour contrôler l’accès à une ressource partagée par plusieurs threads.
Monitor (classe)
La classe System.Threading.Monitor accorde l’accès mutuellement exclusif à une ressource partagée par l’acquisition ou la libération d’un verrou sur l’objet qui identifie la ressource. Tant qu’un verrou est maintenu, le thread qui contient le verrou peut à nouveau obtenir et libérer le verrou. Tout autre thread se voit bloquer l’acquisition du verrou et la méthode Monitor.Enter attend que ce dernier soit libéré. La méthode Enter acquiert un verrou libéré. Vous pouvez également utiliser la méthode Monitor.TryEnter pour spécifier la quantité de temps pendant lequel un thread tente d’acquérir un verrou. Étant donné que la classe Monitor possède l’affinité de thread, le thread qui a acquis un verrou doit le libérer en appelant la méthode Monitor.Exit.
Vous pouvez coordonner l’interaction des threads qui acquièrent un verrou sur le même objet à l’aide des méthodes Monitor.Wait, Monitor.Pulse et Monitor.PulseAll.
Pour plus d'informations, consultez la référence d’API Monitor.
Notes
Utilisez l’instruction lock dans C# et l’instruction SyncLock dans Visual Basic pour synchroniser l’accès à une ressource partagée au lieu d’utiliser la classe Monitor directement. Ces instructions sont implémentées à l’aide des méthodes Enter et Exit et d’un bloc try…finally
pour garantir que le verrou acquis est toujours libéré.
Mutex (classe)
La classe System.Threading.Mutex, telle que Monitor, accorde un accès exclusif à une ressource partagée. Utilisez une des surcharges de méthode Mutex.WaitOne pour demander la propriété d’un mutex. Comme Monitor, Mutex possède l’affinité de thread et le thread qui a acquis un mutex doit le libérer en appelant la méthode Mutex.ReleaseMutex.
Contrairement à Monitor, la classe Mutex peut être utilisée pour la synchronisation entre processus. Pour ce faire, utilisez un mutex nommé, qui est visible dans tout le système d’exploitation. Pour créer une instance de mutex nommé, utilisez un constructeur de mutex qui spécifie un nom. Vous pouvez également appeler la méthode Mutex.OpenExisting pour ouvrir un mutex de système nommé existant.
Pour plus d’informations, consultez l’article Mutex et la référence d’API Mutex.
Structure SpinLock
La structure System.Threading.SpinLock, comme Monitor, accorde un accès exclusif à une ressource partagée, basée sur la disponibilité d’un verrou. Lorsque SpinLock tente d’acquérir un verrou qui n’est pas disponible, il est conservé dans une boucle et effectue des vérifications à plusieurs reprises jusqu'à ce que le verrou devienne disponible.
Pour plus d’informations sur les avantages et les inconvénients de l’utilisation de verrou de rotation, consultez l’article SpinLock et la référence d’API SpinLock.
Classe ReaderWriterLockSlim
La classe System.Threading.ReaderWriterLockSlim accorde un accès exclusif à une ressource partagée pour l’écriture et permet à plusieurs threads d’accéder à la ressource simultanément pour la lecture. Vous souhaiterez peut-être utiliser ReaderWriterLockSlim pour synchroniser l’accès à une structure de données partagée qui prend en charge les opérations de lecture thread-safe, mais nécessite un accès exclusif pour effectuer l’opération d’écriture. Quand un thread demande un accès exclusif (par exemple, en appelant la méthode ReaderWriterLockSlim.EnterWriteLock), les demandes suivantes des lecteurs et enregistreurs sont bloquées jusqu’à ce que tous les lecteurs existants aient quitté le verrou et que l’enregistreur soit entré et sorti du verrou.
Pour plus d'informations, consultez la référence d’API ReaderWriterLockSlim.
Classes Sémaphore et SemaphoreSlim
Les classes System.Threading.Semaphore et System.Threading.SemaphoreSlim limitent le nombre de threads pouvant accéder simultanément à une ressource partagée ou à un pool de ressources. Les autres threads qui demandent la ressource attendent qu’un thread libère le sémaphore. Étant donné que le sémaphore n’a pas d’affinité de thread, un thread peut acquérir le sémaphore et un autre peut le libérer.
SemaphoreSlim est une alternative légère à Semaphore et ne peut être utilisé que pour la synchronisation dans une limite de processus unique.
Sur Windows, vous pouvez utiliser Semaphore pour la synchronisation entre processus. Pour ce faire, créez une instance Semaphore qui représente un sémaphore système nommé à l’aide d’un des constructeurs de sémaphores qui spécifient un nom ou de la méthode Semaphore.OpenExisting. SemaphoreSlim ne prend pas en charge les sémaphores système nommés.
Pour plus d’informations, consultez l’article Sémaphore et SemaphoreSlim et la référence d’API Semaphore ou SemaphoreSlim.
Interaction des threads ou signalisation
Interaction des threads (ou signalisation des threads) signifie qu’un thread doit attendre la notification ou un signal d’un ou de plusieurs threads pour pouvoir continuer. Par exemple, si le thread A appelle la méthode Thread.Join du thread B, le thread A est bloqué jusqu'à ce que le thread B soit terminé. Les primitives de synchronisation décrites dans la section précédente fournissent un mécanisme différent pour la signalisation : en libérant un verrou, un thread indique à un autre thread qu’il peut poursuivre en acquérant le verrou.
Cette section décrit des constructions de signalisation supplémentaires fournies par .NET.
Classes EventWaitHandle, AutoResetEvent, ManualResetEvent et ManualResetEventSlim
La classe System.Threading.EventWaitHandle représente un événement de synchronisation de threads.
Un événement de synchronisation peut être dans un état non signalé ou signalé. Lorsque l’état d’un événement est non signalé, un thread qui appelle la surcharge WaitOne de l’événement est bloqué jusqu'à ce qu’un événement soit signalé. La méthode EventWaitHandle.Set définit l’état d’un événement sur « signalé ».
Le comportement d’un EventWaitHandle qui a été signalé dépend de son mode de réinitialisation :
- Un EventWaitHandle créé avec l’indicateur EventResetMode.AutoReset se réinitialise automatiquement après avoir libéré un seul thread en attente. C'est comme un tourniquet qui ne laisse passer qu'un seul thread à chaque fois qu'il est signalé. La classe System.Threading.AutoResetEvent, qui est dérivée de EventWaitHandle, représente ce comportement.
- Un EventWaitHandle créé avec l’indicateur EventResetMode.ManualReset reste signalé jusqu'à ce que la méthode Reset soit appelée. C’est comme une barrière qui reste fermée jusqu'au signalement, puis ouverte jusqu'à ce que quelqu'un la ferme. La classe System.Threading.ManualResetEvent, qui est dérivée de EventWaitHandle, représente ce comportement. La classe System.Threading.ManualResetEventSlim est une alternative légère à ManualResetEvent.
Sur Windows, vous pouvez utiliser EventWaitHandle pour la synchronisation entre processus. Pour ce faire, créez une instance EventWaitHandle qui représente un événement de synchronisation système nommé à l’aide d’un des constructeurs EventWaitHandle, qui spécifie un nom ou de la méthode EventWaitHandle.OpenExisting.
Pour plus d’informations, consultez l’article EventWaitHandle. Pour la référence d’API, consultez EventWaitHandle, AutoResetEvent, ManualResetEvent et ManualResetEventSlim.
Classe CountdownEvent
La classe System.Threading.CountdownEvent représente un événement défini quand son nombre est égal à zéro. Bien que CountdownEvent.CurrentCount soit supérieure à zéro, un thread qui appelle CountdownEvent.Wait est bloqué. Appelez CountdownEvent.Signal pour décrémenter le nombre d’événements.
Contrairement à ManualResetEvent ou à ManualResetEventSlim, que vous pouvez utiliser pour débloquer plusieurs threads avec un signal d’un thread, vous pouvez utiliser CountdownEvent pour débloquer un ou plusieurs threads avec des signaux de plusieurs threads.
Pour plus d’informations, consultez l’article CountdownEvent et la référence d’API CountdownEvent.
Classe de cloisonnement
La classe System.Threading.Barrier représente une barrière de l’exécution du thread. Un thread qui appelle la méthode Barrier.SignalAndWait signale qu’il a atteint le cloisonnement et attend que d’autres threads participants atteignent également le cloisonnement. Lorsque tous les threads participants atteignent le cloisonnement, ils continuent. La barrière est réinitialisée et peut être à nouveau utilisée.
Vous pouvez utiliser Barrier quand un ou plusieurs threads requièrent les résultats d’autres threads avant de passer à la phase de calcul suivante.
Pour plus d’informations, consultez l’article Cloisonnement et la référence d’API Barrier.
Interlocked (classe)
La classe System.Threading.Interlocked fournit des méthodes statiques qui effectuent des opérations atomiques simples sur une variable. Ces opérations atomiques incluent l'addition, l’incrémentation et la décrémentation, l’échange et l’échange conditionnel en fonction d'une comparaison, ainsi qu’une opération de lecture d’une valeur entière 64 bits.
Pour plus d'informations, consultez la référence d’API Interlocked.
Structure SpinWait
La structure System.Threading.SpinWait prend en charge l'attente basée sur les spins. Vous voudrez peut-être l’utiliser quand un thread doit attendre le signalement d'un événement ou la satisfaction d'une condition, mais que le temps d'attente réel est supposé être inférieur à la latence requise, en utilisant un descripteur d'attente ou en bloquant le thread. À l'aide de SpinWait, vous pouvez spécifier une courte période de rotation pendant l'attente, puis générer (par exemple, en attente ou en veille) uniquement si la condition n'a pas été remplie dans le délai spécifié.
Pour plus d’informations, consultez l’article SpinWait et la référence d’API SpinWait.