Mot clé C# Volatile

Le mot clé volatile indique qu’un champ peut être modifié par plusieurs threads qui s’exécutent simultanément. Les champs qui sont déclarés volatile ne sont pas soumis aux optimisations du compilateur qui supposent l’accès par un seul thread. Cela garantit que la valeur la plus à jour est présente dans le champ à tout moment.

Rien ne garantit l’ordre d’exécution d’une instruction par rapport à une autre lorsqu’on effectue 2 opérations de lecture ou 2 opérations d’écriture sur des variables différentes. Après optimisation du compilateur, les instructions peuvent très bien être inversées. En effet une instruction dans le code équivaut à plusieurs instructions en langage machine. Ainsi dans le cadre d’une exécution du code par 2 threads séparées, l’exécution peut mener à des erreurs si on utilise les variables dans les 2 threads.

Pour palier à ce problème, on peut utiliser le mot clé VOLATILE qui permet de garantir l’ordre d’exécution d’une variable par rapport à une autre:
Dans le cas d’écritures, vous avez une écriture ordinaire suivie d’une écriture volatile, et une écriture volatile ne peut pas être réorganisée lorsqu’elle est précédée d’une opération de mémoire.
Dans le cas de lectures, vous avez une lecture volatile suivie d’une lecture ordinaire, et une lecture volatile ne peut pas être réorganisée lorsqu’elle est suivie d’une opération de mémoire.

L’exemple suivant permet d’illustrer une bonne utilisation du mot clé:

public class DataInit 
{ 
   private int _data = 0; 
   private volatile bool _initialized = false; 

   void Init() 
   { 
     _data = 42; // Write 1 
     _initialized = true; // Write 2 
   } 

   void Print() 
   { 
     if (_initialized) { // Read 1 
       Console.WriteLine(_data); // Read 2 
     } 
     else { 
       Console.WriteLine("Not initialized"); 
     } 
   } 
} 

D’une façon générale, il est fortement conseillé d’utiliser des classes du framework pour manipuler des mêmes variables dans des threads séparées:
Valeurs initialisées tardivement: Lazy<T>; LazyInitializer
Collections thread-safe: BlockingCollection<T>; ConcurrentBag<T>; ConcurrentDictionary<TKey,TValue>; ConcurrentQueue<T>; ConcurrentStack<T>
Primitives permettant de coordonner l’exécution de différents threads: AutoResetEvent; Barrier; CountdownEvent; ManualResetEventSlim; Monitor; SemaphoreSlim
Conteneur conservant une valeur séparée pour chaque thread: ThreadLocal<T>

D’une façon générale, les bonnes pratiques sont:
Évitez d’utiliser inutilement des champs volatiles: le plus souvent, des verrous ou des collections simultanées (System.Collections.Concurrent.*) conviennent mieux pour l’échange de données entre threads. Dans certains cas, des champs volatiles peuvent être utilisés pour optimiser du code simultané mais vous devriez utiliser des mesures de performance afin de valider le fait que l’avantage l’emporte sur le surcroît de complexité.
– Au lieu d’implémenter le modèle d’initialisation tardive vous-même en utilisant un champ volatile, utilisez les types System.Lazy<T> et System.Threading.LazyInitializer.
Évitez les boucles d’interrogation: vous pouvez souvent utiliser BlockingCollection<T>, Monitor.Wait/Pulse, des événements ou une programmation asynchrone à la place d’une boucle d’interrogation.
– Dès que cela est possible, utilisez les primitives de simultanéité .NET standards au lieu d’implémenter une fonctionnalité équivalente vous-même.

Leave a Reply