Bonnes pratiques pour le threading managéManaged threading best practices

Le multithreading nécessite une programmation attentive.Multithreading requires careful programming. Pour réduire la complexité de la plupart des tâches, il vous suffit de mettre en file d’attente les requêtes à exécuter par les threads d’un pool de threads.For most tasks, you can reduce complexity by queuing requests for execution by thread pool threads. Cet article vous permet de remédier aux situations plus complexes, telles que la coordination du travail de plusieurs threads ou la gestion des threads bloqués.This topic addresses more difficult situations, such as coordinating the work of multiple threads, or handling threads that block.

Notes

À partir de .NET Framework 4, la bibliothèque parallèle de tâches et PLINQ fournissent des API qui atténuent une partie de la complexité et des risques de la programmation multithread.Starting with the .NET Framework 4, the Task Parallel Library and PLINQ provide APIs that reduce some of the complexity and risks of multi-threaded programming. Pour plus d’informations, consultez la page Programmation parallèle dans .NET.For more information, see Parallel Programming in .NET.

Interblocages et conditions de concurrenceDeadlocks and race conditions

Le multithreading résout les problèmes de débit et de réactivité, mais, ce faisant, occasionne de nouveaux problèmes : les interblocages et les conditions de concurrence.Multithreading solves problems with throughput and responsiveness, but in doing so it introduces new problems: deadlocks and race conditions.

BlocagesDeadlocks

Un interblocage se produit lorsque chacun des deux threads tente de verrouiller une ressource déjà verrouillée par l’autre thread.A deadlock occurs when each of two threads tries to lock a resource the other has already locked. Aucun des deux threads ne peut donc poursuivre l’exécution.Neither thread can make any further progress.

De nombreuses méthodes des classes de threading managé fournissent des délais d’expiration conçus pour faciliter la détection des interblocages.Many methods of the managed threading classes provide time-outs to help you detect deadlocks. Par exemple, le code ci-après tente d’acquérir un verrou sur un objet nommé lockObject.For example, the following code attempts to acquire a lock on an object named lockObject. Si le verrou n’est pas obtenu en 300 millisecondes, Monitor.TryEnter retourne false.If the lock is not obtained in 300 milliseconds, Monitor.TryEnter returns false.

If Monitor.TryEnter(lockObject, 300) Then  
    Try  
        ' Place code protected by the Monitor here.  
    Finally  
        Monitor.Exit(lockObject)  
    End Try  
Else  
    ' Code to execute if the attempt times out.  
End If  
if (Monitor.TryEnter(lockObject, 300)) {  
    try {  
        // Place code protected by the Monitor here.  
    }  
    finally {  
        Monitor.Exit(lockObject);  
    }  
}  
else {  
    // Code to execute if the attempt times out.  
}  

Conditions de concurrenceRace conditions

Une condition de concurrence est un bogue qui survient lorsque le résultat d’un programme dépend du thread qui atteint le premier un bloc de code spécifique.A race condition is a bug that occurs when the outcome of a program depends on which of two or more threads reaches a particular block of code first. L’exécution du programme à plusieurs reprises produit des résultats différents, et le résultat d’une exécution donnée n’est donc pas prévisible.Running the program many times produces different results, and the result of any given run cannot be predicted.

Un exemple simple de condition de concurrence correspond à l’incrémentation d’un champ.A simple example of a race condition is incrementing a field. Supposons qu’une classe comporte un champ static privé (Shared en Visual Basic) qui est incrémenté chaque fois qu’une instance de la classe est créée, à l’aide d’un code tel que objCt++; (C#) ou objCt += 1 (Visual Basic).Suppose a class has a private static field (Shared in Visual Basic) that is incremented every time an instance of the class is created, using code such as objCt++; (C#) or objCt += 1 (Visual Basic). Cette opération nécessite le chargement de la valeur de objCt dans un registre, l’incrémentation de la valeur, puis son stockage dans objCt.This operation requires loading the value from objCt into a register, incrementing the value, and storing it in objCt.

Dans une application multithread, il est possible qu’un thread ayant chargé et incrémenté la valeur soit devancé par un autre thread qui exécute ces trois étapes ; lorsque le premier thread reprend l’exécution et stocke sa valeur, il remplace alors la valeur de objCt sans tenir compte du fait qu’elle a changé dans l’intervalle.In a multithreaded application, a thread that has loaded and incremented the value might be preempted by another thread which performs all three steps; when the first thread resumes execution and stores its value, it overwrites objCt without taking into account the fact that the value has changed in the interim.

Cette condition de concurrence particulière est facile à éviter en utilisant des méthodes de la classe Interlocked, telles que Interlocked.Increment.This particular race condition is easily avoided by using methods of the Interlocked class, such as Interlocked.Increment. Pour découvrir d’autres techniques de synchronisation des données entre plusieurs threads, consultez l’article Synchronisation des données pour le multithreading.To read about other techniques for synchronizing data among multiple threads, see Synchronizing Data for Multithreading.

Des conditions de concurrence peuvent également survenir lorsque vous synchronisez les activités de plusieurs threads.Race conditions can also occur when you synchronize the activities of multiple threads. Chaque fois que vous écrivez une ligne de code, vous devez prendre en compte ce qui peut se produire si un thread est devancé par un autre thread avant d’avoir exécuté la ligne (ou avant toute instruction machine individuelle composant la ligne).Whenever you write a line of code, you must consider what might happen if a thread were preempted before executing the line (or before any of the individual machine instructions that make up the line), and another thread overtook it.

Membres static et constructeurs staticStatic members and static constructors

Une classe n’est pas initialisée tant que son constructeur de classe (constructeur static en C#, Shared Sub New en Visual Basic) n’a pas fini de s’exécuter.A class is not initialized until its class constructor (static constructor in C#, Shared Sub New in Visual Basic) has finished running. Pour empêcher l’exécution de code sur un type qui n’est pas initialisé, le common language runtime bloque tous les appels d’autres threads aux membres static de la classe (membres Shared en Visual Basic) jusqu’à la fin de l’exécution du constructeur de classe.To prevent the execution of code on a type that is not initialized, the common language runtime blocks all calls from other threads to static members of the class (Shared members in Visual Basic) until the class constructor has finished running.

Par exemple, si un constructeur de classe démarre un nouveau thread, et que la procédure de thread appelle un membre static de la classe, le nouveau thread se bloque jusqu’à la fin du constructeur de classe.For example, if a class constructor starts a new thread, and the thread procedure calls a static member of the class, the new thread blocks until the class constructor completes.

Cela s’applique à n’importe quel type pouvant comporter un constructeur static.This applies to any type that can have a static constructor.

Nombre de processeursNumber of processors

Le fait que plusieurs processeurs ou un seul soient disponibles sur un système peut influencer l’architecture multithread.Whether there are multiple processors or only one processor available on a system can influence multithreaded architecture. Pour plus d’informations, consultez Nombre de processeurs.For more information, see Number of Processors.

Utilisez la Environment.ProcessorCount propriété pour déterminer le nombre de processeurs disponibles au moment de l’exécution.Use the Environment.ProcessorCount property to determine the number of processors available at run time.

Recommandations généralesGeneral recommendations

Lorsque vous utilisez plusieurs threads, tenez compte des recommandations suivantes :Consider the following guidelines when using multiple threads:

  • N’utilisez pas Thread.Abort pour mettre fin à d’autres threads.Don't use Thread.Abort to terminate other threads. L’appel de la méthode Abort sur un autre thread équivaut à lever une exception sur ce dernier sans connaître le stade précis du traitement atteint par ce thread.Calling Abort on another thread is akin to throwing an exception on that thread, without knowing what point that thread has reached in its processing.

  • N’utilisez pas Thread.Suspend et Thread.Resume pour synchroniser les activités de plusieurs threads.Don't use Thread.Suspend and Thread.Resume to synchronize the activities of multiple threads. Utilisez Mutex, ManualResetEvent, AutoResetEvent et Monitor.Do use Mutex, ManualResetEvent, AutoResetEvent, and Monitor.

  • Ne contrôlez pas l’exécution des threads de travail à partir de votre programme principal (à l’aide d’événements, par exemple).Don't control the execution of worker threads from your main program (using events, for example). Concevez plutôt votre programme pour que les threads de travail soient chargés d’attendre que le travail devienne disponible, d’exécuter ce travail, puis d’en informer les autres parties de votre programme lorsqu’ils ont terminé.Instead, design your program so that worker threads are responsible for waiting until work is available, executing it, and notifying other parts of your program when finished. Si vos threads de travail ne se bloquent pas, envisagez d’utiliser les threads d’un pool de threads.If your worker threads do not block, consider using thread pool threads. Monitor.PulseAll est utile dans les situations où les threads de travail se bloquent.Monitor.PulseAll is useful in situations where worker threads block.

  • N’utilisez pas de types en tant qu’objets de verrou.Don't use types as lock objects. Autrement dit, évitez un code tel que lock(typeof(X)) en C# ou SyncLock(GetType(X)) en Visual Basic, ou l’utilisation de Monitor.Enter avec des objets Type.That is, avoid code such as lock(typeof(X)) in C# or SyncLock(GetType(X)) in Visual Basic, or the use of Monitor.Enter with Type objects. Pour un type donné, il existe une seule instance de System.Type par domaine d’application.For a given type, there is only one instance of System.Type per application domain. Si le type sur lequel vous utilisez un verrou est public, un code différent du vôtre peut utiliser des verrous sur ce type, entraînant ainsi des interblocages.If the type you take a lock on is public, code other than your own can take locks on it, leading to deadlocks. Pour découvrir les autres problèmes, consultez l’article Meilleures pratiques pour la fiabilité.For additional issues, see Reliability Best Practices.

  • Procédez avec précaution lorsque vous utilisez des verrous sur des instances, par exemple lock(this) en C# ou SyncLock(Me) en Visual Basic.Use caution when locking on instances, for example lock(this) in C# or SyncLock(Me) in Visual Basic. Si un autre code de votre application, externe au type, utilise un verrou sur l’objet, des interblocages risquent de se produire.If other code in your application, external to the type, takes a lock on the object, deadlocks could occur.

  • Assurez-vous qu’un thread entré dans un moniteur quitte toujours ce dernier, même si une exception se produit pendant que le thread se trouve dans le moniteur.Do ensure that a thread that has entered a monitor always leaves that monitor, even if an exception occurs while the thread is in the monitor. L’instruction C# lock et l’instruction Visual Basic SyncLock assurent automatiquement ce comportement, en employant un bloc finally pour garantir l’appel de la méthode Monitor.Exit.The C# lock statement and the Visual Basic SyncLock statement provide this behavior automatically, employing a finally block to ensure that Monitor.Exit is called. Si vous n’êtes pas en mesure de garantir l’appel de la méthode Exit, vous pouvez modifier votre conception de façon à utiliser Mutex.If you cannot ensure that Exit will be called, consider changing your design to use Mutex. Un mutex est automatiquement libéré lorsque le thread auquel il appartient a terminé.A mutex is automatically released when the thread that currently owns it terminates.

  • Utilisez plusieurs threads pour les tâches qui nécessitent des ressources différentes, et évitez d’attribuer plusieurs threads à une même ressource.Do use multiple threads for tasks that require different resources, and avoid assigning multiple threads to a single resource. Par exemple, vous tirerez avantage du fait que les tâches impliquant des E/S disposent de leur propre thread, car ce thread se bloquera lors des opérations d’E/S et permettra ainsi à d’autres threads de s’exécuter.For example, any task involving I/O benefits from having its own thread, because that thread will block during I/O operations and thus allow other threads to execute. L’entrée utilisateur est une autre ressource tirant profit d’un thread dédié.User input is another resource that benefits from a dedicated thread. Sur un ordinateur monoprocesseur, une tâche qui exige de multiples calculs coexiste avec l’entrée utilisateur et avec les tâches qui impliquent des E/S, mais plusieurs tâches nécessitant de nombreux calculs entrent en concurrence les unes avec les autres.On a single-processor computer, a task that involves intensive computation coexists with user input and with tasks that involve I/O, but multiple computation-intensive tasks contend with each other.

  • Envisagez d’utiliser les méthodes de la classe Interlocked pour les modifications d’état simples, plutôt que de recourir à l’instruction lock (SyncLock en Visual Basic).Consider using methods of the Interlocked class for simple state changes, instead of using the lock statement (SyncLock in Visual Basic). L’instruction lock est un outil à usage général efficace, mais la classe Interlocked offre de meilleures performances pour les mises à jour qui doivent être atomiques.The lock statement is a good general-purpose tool, but the Interlocked class provides better performance for updates that must be atomic. En interne, elle exécute un seul préfixe de verrou s’il n’existe aucun conflit.Internally, it executes a single lock prefix if there is no contention. Lors des phases de révision du code, examinez le code semblable à celui des exemples ci-dessous.In code reviews, watch for code like that shown in the following examples. Dans le premier exemple, une variable d’état est incrémentée :In the first example, a state variable is incremented:

    SyncLock lockObject  
        myField += 1  
    End SyncLock  
    
    lock(lockObject)
    {  
        myField++;  
    }  
    

    Vous pouvez améliorer les performances en utilisant la méthode Increment au lieu de l’instructionlock, comme suit :You can improve performance by using the Increment method instead of the lock statement, as follows:

    System.Threading.Interlocked.Increment(myField)  
    
    System.Threading.Interlocked.Increment(myField);  
    

    Notes

    Dans .NET Framework 2.0 et les versions ultérieures, utilisez la méthode Add pour les incréments atomiques supérieurs à 1.In the .NET Framework 2.0 and later, use the Add method for atomic increments larger than 1.

    Dans le second exemple, une variable de type référence est uniquement mise à jour s’il s’agit d’une référence null (Nothing en Visual Basic).In the second example, a reference type variable is updated only if it is a null reference (Nothing in Visual Basic).

    If x Is Nothing Then  
        SyncLock lockObject  
            If x Is Nothing Then  
                x = y  
            End If  
        End SyncLock  
    End If  
    
    if (x == null)  
    {  
        lock (lockObject)  
        {  
            x ??= y;
        }  
    }  
    

    Les performances peuvent être améliorées en utilisant à la place la méthode CompareExchange comme suit :Performance can be improved by using the CompareExchange method instead, as follows:

    System.Threading.Interlocked.CompareExchange(x, y, Nothing)  
    
    System.Threading.Interlocked.CompareExchange(ref x, y, null);  
    

    Notes

    À compter de .NET Framework 2.0, la surcharge de méthode CompareExchange<T>(T, T, T) fournit une alternative de type sécurisé pour les types référence.Beginning with .NET Framework 2.0, the CompareExchange<T>(T, T, T) method overload provides a type-safe alternative for reference types.

Recommandations relatives aux bibliothèques de classesRecommendations for class libraries

Lorsque vous concevez des bibliothèques de classes pour le multithreading, tenez compte des recommandations suivantes :Consider the following guidelines when designing class libraries for multithreading:

  • Dans la mesure du possible, évitez toute nécessité de synchronisation.Avoid the need for synchronization, if possible. Cette consigne s’applique tout particulièrement pour le code très sollicité.This is especially true for heavily used code. Par exemple, un algorithme peut être ajusté de façon à tolérer une condition de concurrence plutôt que de l’éliminer.For example, an algorithm might be adjusted to tolerate a race condition rather than eliminate it. Une synchronisation inutile dégrade les performances et entraîne un risque d’interblocages et de conditions de concurrence.Unnecessary synchronization decreases performance and creates the possibility of deadlocks and race conditions.

  • Définissez les données static (Shared en Visual Basic) comme thread-safe par défaut.Make static data (Shared in Visual Basic) thread safe by default.

  • Ne définissez pas les données d’instance comme thread-safe par défaut.Do not make instance data thread safe by default. L’ajout de verrous pour créer un code thread-safe diminue les performances, multiplie les conflits de verrou et occasionne un risque d’interblocages.Adding locks to create thread-safe code decreases performance, increases lock contention, and creates the possibility for deadlocks to occur. Dans les modèles d’application communs, le code utilisateur n’est exécuté que par un seul thread à la fois, ce qui minimise la nécessité d’une cohérence de thread.In common application models, only one thread at a time executes user code, which minimizes the need for thread safety. C’est la raison pour laquelle les bibliothèques de classes .NET Framework ne sont pas thread-safe par défaut.For this reason, the .NET Framework class libraries are not thread safe by default.

  • Évitez de fournir des méthodes statiques qui modifient l’état statique.Avoid providing static methods that alter static state. Dans les scénarios de serveur courants, l’état statique est partagé entre les requêtes, ce qui signifie que plusieurs threads peuvent exécuter ce code en même temps.In common server scenarios, static state is shared across requests, which means multiple threads can execute that code at the same time. Ceci occasionne un risque de bogues de threading.This opens up the possibility of threading bugs. Envisagez d’utiliser un modèle de conception qui encapsule les données dans des instances non partagées entre les requêtes.Consider using a design pattern that encapsulates data into instances that are not shared across requests. En outre, si les données static sont synchronisées, les appels entre les méthodes statiques qui modifient l’état peuvent entraîner des interblocages ou une synchronisation redondante et dégrader ainsi les performances.Furthermore, if static data are synchronized, calls between static methods that alter state can result in deadlocks or redundant synchronization, adversely affecting performance.

Voir aussiSee also