Meilleures pratiques pour la synchronisation des threads .Net

La synchronisation est un concept utilisé pour empêcher plusieurs threads d'accéder simultanément à une ressource partagée. Vous pouvez l'utiliser pour empêcher plusieurs threads d'appeler simultanément les propriétés ou les méthodes d'un objet. Tout ce que vous avez à faire est de synchroniser le bloc de code qui accède à la ressource partagée ou de synchroniser les appels aux propriétés et aux membres de l'objet afin qu'à un moment donné, un seul thread puisse entrer dans la section critique.

Cet article présente une discussion sur les concepts liés à la synchronisation et la sécurité des threads dans .Net et les meilleures pratiques impliquées.

Serrure exclusive

Le verrouillage exclusif est utilisé pour garantir qu'à tout moment donné, un et un seul thread peut entrer dans une section critique. Vous devez utiliser l'un des éléments suivants pour implémenter des verrous exclusifs dans votre application.

  • Lock - il s'agit d'un raccourci syntaxique pour les méthodes statiques de la classe Monitor et est utilisé pour acquérir un verrou exclusif sur une ressource partagée
  • Mutex - similaire au mot-clé lock sauf qu'il peut fonctionner sur plusieurs processus
  • SpinLock - utilisé pour acquérir un verrou exclusif sur une ressource partagée en évitant la surcharge de changement de contexte de thread

Vous pouvez utiliser les méthodes statiques de la classe Monitor ou le mot clé lock pour implémenter la sécurité des threads dans vos applications. Les membres statiques de la classe Monitor et les mots clés de verrouillage peuvent être utilisés pour empêcher l'accès simultané à une ressource partagée. Le mot clé lock n'est qu'un raccourci pour implémenter la synchronisation. Cependant, lorsque vous devez effectuer des opérations complexes dans une application multithread, les méthodes Wait () et Pulse () de la classe Monitor peuvent être utiles.

L'extrait de code suivant illustre comment vous pouvez implémenter la synchronisation à l'aide de la classe Monitor.

private static readonly object lockObj = new object();

        static void Main(string[] args)

        {

            Monitor.Enter(lockObj);

                       try

            {

               //Some code

            }

                  finally

            {

                Monitor.Exit(lockObj);

            }

        }

Le code équivalent utilisant le mot-clé lock ressemblera à ceci:

    private static readonly object lockObj = new object();

        static void Main(string[] args)

        {  

            try

            {

                lock(lockObj)

                {

                    //Some code

                }             

            }

            finally

            {

                //You can release any resources here

            }

        }

Vous pouvez tirer parti de la classe Mutex pour implémenter une synchronisation qui peut s'étendre sur plusieurs processus. Notez que, comme pour l'instruction lock, un verrou acquis par un Mutex ne peut être libéré qu'à partir du même thread qui a été utilisé pour acquérir le verrou. L'acquisition et la libération de verrous à l'aide de Mutex sont comparativement plus lentes que de faire de même avec l'instruction lock.

L'idée principale derrière SpinLock est de minimiser le coût impliqué dans le changement de contexte entre les threads - si un thread peut attendre ou tourner pendant un certain temps jusqu'à ce qu'il puisse acquérir un verrou sur une ressource partagée, la surcharge impliquée dans le changement de contexte entre les threads peut être évitée . Lorsque la section critique effectue une quantité minimale de travail, elle peut être un bon candidat pour un SpinLock.

Verrou non exclusif

Vous pouvez profiter du verrouillage non exclusif pour limiter la concurrence. Pour implémenter des verrous non exclusifs, vous pouvez utiliser l'un des éléments suivants.

  • Sémaphore - utilisé pour limiter le nombre de threads pouvant accéder simultanément à une ressource partagée. Essentiellement, il est utilisé pour limiter simultanément le nombre de consommateurs pour une ressource partagée particulière.
  • SemaphoreSlim - une alternative rapide et légère à la classe Semaphore pour implémenter des verrous non exclusifs.
  • ReaderWriterLockSlim - la classe ReaderWriterLockSlim a été introduite dans .Net Framework 3.5 en remplacement de la classe ReaderWriterLock.

Vous pouvez utiliser la classe ReaderWriterLockSlim pour acquérir un verrou non exclusif sur une ressource partagée qui nécessiterait des lectures fréquentes mais des mises à jour peu fréquentes. Ainsi, au lieu d'un verrou mutuellement exclusif sur une ressource partagée nécessitant des lectures fréquentes et des mises à jour peu fréquentes, vous pouvez utiliser cette classe pour acquérir un verrou de lecture sur la ressource partagée et un verrou d'écriture exclusif sur celle-ci.

Deadlocks

Vous devez éviter d'utiliser une instruction de verrouillage sur le type ou d'utiliser des instructions comme lock (this) pour implémenter la synchronisation dans votre application, car cela pourrait entraîner des blocages. Notez que des blocages peuvent également survenir si vous maintenez le verrou acquis sur une ressource partagée pendant une période plus longue. Vous ne devez pas utiliser de types immuables dans vos instructions de verrouillage. Par exemple, vous devez éviter d'utiliser un objet chaîne comme clé dans votre instruction de verrouillage. Vous devez éviter d'utiliser l'instruction lock sur un type public - il est recommandé de verrouiller les objets privés ou protégés qui ne sont pas internés. En substance, une situation de blocage se produit lorsque plusieurs threads attendent l'un l'autre pour libérer le verrou sur une ressource partagée. Vous pouvez vous référer à cet article MSDN pour en savoir plus sur les blocages.