volatile (référence C#)

Le mot clé volatile indique qu’un champ peut être modifié par plusieurs threads qui s’exécutent simultanément. Le compilateur, le système de runtime et même le matériel sont susceptibles de réorganiser les lectures et les écritures sur des emplacements de mémoire pour des raisons de performances. Les champs déclarés volatile sont exclus de certains types d’optimisations. Rien ne garantit que les écritures volatiles présentent un ordre total unique, tel que le voient tous les threads d’exécution. Pour plus d'informations, consultez la classe Volatile.

Notes

Sur un système multiprocesseur, une opération de lecture volatile ne garantit pas l’obtention de la dernière valeur écrite dans cet emplacement de mémoire par un processeur. De même, une opération d’écriture volatile ne garantit pas que la valeur écrite serait immédiatement visible pour les autres processeurs.

Le mot clé volatile peut être appliqué aux champs des types suivants :

  • Types référence.
  • Types pointeur (dans un contexte unsafe). Notez que, même si le pointeur lui-même peut être volatile, l’objet sur lequel il pointe ne le peut pas. En d’autres termes, vous ne pouvez pas déclarer un pointeur vers un objet volatile.
  • Types simples comme sbyte, byte, short, ushort, int, uint, char, float et bool.
  • Type enum avec l’un des types de base suivants : byte, sbyte, short, ushort, int ou uint.
  • Paramètres de type générique connus comme des types référence.
  • Voir IntPtr et UIntPtr.

Les autres types, notamment double et long, ne peuvent pas être marqués volatile, car il n’y a aucune garantie que les lectures et écritures sur des champs de ce type soient atomiques. Pour protéger l’accès multithread à ces types de champs, utilisez les membres de classe Interlocked ou l’instruction lock.

Le mot clé volatile ne peut s’appliquer qu’aux champs d’une class ou d’un struct. Les variables locales ne peuvent pas être déclarées volatile.

Exemple

L’exemple ci-dessous montre comment déclarer une variable de champ public comme volatile.

class VolatileTest
{
    public volatile int sharedStorage;

    public void Test(int i)
    {
        sharedStorage = i;
    }
}

L’exemple suivant montre comment il est possible de créer un thread auxiliaire ou de travail et de l’utiliser pour effectuer le traitement en parallèle avec le thread principal. Pour plus d'informations sur le multithreading, voir Threading managé.

public class Worker
{
    // This method is called when the thread is started.
    public void DoWork()
    {
        bool work = false;
        while (!_shouldStop)
        {
            work = !work; // simulate some work
        }
        Console.WriteLine("Worker thread: terminating gracefully.");
    }
    public void RequestStop()
    {
        _shouldStop = true;
    }
    // Keyword volatile is used as a hint to the compiler that this data
    // member is accessed by multiple threads.
    private volatile bool _shouldStop;
}

public class WorkerThreadExample
{
    public static void Main()
    {
        // Create the worker thread object. This does not start the thread.
        Worker workerObject = new Worker();
        Thread workerThread = new Thread(workerObject.DoWork);

        // Start the worker thread.
        workerThread.Start();
        Console.WriteLine("Main thread: starting worker thread...");

        // Loop until the worker thread activates.
        while (!workerThread.IsAlive)
            ;

        // Put the main thread to sleep for 500 milliseconds to
        // allow the worker thread to do some work.
        Thread.Sleep(500);

        // Request that the worker thread stop itself.
        workerObject.RequestStop();

        // Use the Thread.Join method to block the current thread
        // until the object's thread terminates.
        workerThread.Join();
        Console.WriteLine("Main thread: worker thread has terminated.");
    }
    // Sample output:
    // Main thread: starting worker thread...
    // Worker thread: terminating gracefully.
    // Main thread: worker thread has terminated.
}

Si vous ajoutez le modificateur volatile à la déclaration de _shouldStop en place, vous obtiendrez toujours les mêmes résultats (similaires à l’extrait indiqué dans le code précédent). Sans ce modificateur sur le membre _shouldStop en revanche, le comportement est imprévisible. La méthode DoWork peut optimiser l’accès au membre, ce qui entraîne la lecture de données périmées. En raison de la nature de la programmation multithread, le nombre de lectures obsolètes est imprévisible. Différentes exécutions du programme produiront des résultats légèrement différents.

spécification du langage C#

Pour plus d'informations, voir la spécification du langage C#. La spécification du langage est la source de référence pour la syntaxe C# et son utilisation.

Voir aussi