Поделиться через


volatile (справочник по C#)

Ключевое volatile слово указывает, что поле может быть изменено несколькими потоками, выполняющимися одновременно. Компилятор, система выполнения и даже оборудование могут переупорядочивать операции чтения и записи в расположение памяти для повышения производительности. Объявленные volatile поля исключаются из определенных типов оптимизаций. Нет никакой гарантии единого общей упорядочения переменных записей, как показано во всех потоках выполнения. Дополнительные сведения см. в Volatile классе.

Осторожность

Ключевое volatile слово часто неправильно понимается и неправильно используется в многопоточности программирования. В большинстве случаев вместо volatile следует использовать более безопасные и надежные альтернативы. Современная платформа .NET обеспечивает лучшие средства параллелизма, такие как Interlocked класс, lock оператор или примитивы синхронизации более высокого уровня. Эти альтернативные варианты обеспечивают более четкую семантику и более надежные гарантии, чем volatile. Рассмотрите возможность использования volatile только в редких сложных сценариях, где вы полностью понимаете его ограничения и проверили, что это подходящее решение.

Замечание

В многопроцессорной системе переменная операция чтения не гарантирует получение последнего значения, записанного в это расположение памяти любым процессором. Аналогичным образом переменная операция записи не гарантирует, что записанное значение будет немедленно видно другим процессорам.

Ключевое volatile слово можно применить к полям следующих типов:

  • Ссылочные типы.
  • Типы указателей (в небезопасном контексте). Обратите внимание, что хотя сам указатель может быть переменным, объект, на который он указывает, не может. Другими словами, нельзя объявить "указатель на переменную".
  • Простые типы, такие как sbyte, byte, short, ushort, int, uint, char, float и bool.
  • Тип enum с одним из следующих базовых типов: byte, sbyte, short, , ushortintили uint.
  • Параметры универсального типа, известные как ссылочные типы.
  • IntPtr и UIntPtr.

Другие типы, включая double и long, не могут быть помечены volatile , так как операции чтения и записи в поля этих типов не могут быть атомарными. Чтобы обеспечить многопоточную защиту доступа к этим типам полей, используйте Interlocked члены класса или защитите доступ с помощью оператора lock.

Для большинства многопоточных сценариев, даже с поддерживаемыми типами, предпочитайте использовать Interlocked операции, lock операторы или другие примитивы синхронизации вместо volatile. Эти альтернативные варианты менее подвержены тонким ошибкам параллелизма.

Ключевое volatile слово может применяться только к полям class или struct. Локальные переменные нельзя объявить volatile.

Альтернативы ключевому слову volatile

В большинстве случаев следует использовать одну из этих безопасных альтернатив вместо volatileследующих:

  • Interlocked операции: предоставление атомарных операций для числовых типов и ссылочных назначений. Как правило, они быстрее и обеспечивают более надежные гарантии, чем volatile.
  • lock оператор: обеспечивает взаимное исключение и барьеры памяти. Используйте для защиты более крупных критических разделов.
  • Volatile класс: предоставляет явные переменные операции чтения и записи с более четкой семантикой, чем ключевое volatile слово.
  • Примитивы синхронизации более высокого уровня: например ReaderWriterLockSlim, Semaphoreили параллельные коллекции из System.Collections.Concurrent.

Ключевое volatile слово не предоставляет атомарность для операций, отличных от назначения, не предотвращает условия гонки и не предоставляет гарантии заказа для других операций памяти. Эти ограничения делают его непригодным для большинства сценариев параллелизма.

Пример

В следующем примере показано, как объявить переменную общедоступного поля как volatile.

class VolatileTest
{
    public volatile int sharedStorage;

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

В следующем примере показано, как можно создать вспомогательный или рабочий поток и использовать для параллельной обработки с основным потоком. Дополнительные сведения о многопоточности см. в разделе "Управляемый поток".

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.
}

При добавлении модификатора volatile в объявление _shouldStop вы всегда получите одинаковые результаты (аналогично фрагменту, показанному в предыдущем коде). Однако без этого модификатора на _shouldStop элементе поведение непредсказуемо. Метод DoWork может оптимизировать доступ к члену, что может привести к чтению устаревших данных. Из-за характера многопотокового программирования число устаревших операций чтения непредсказуемо. Различные запуски программы будут производить несколько разные результаты.

Спецификация языка C#

Дополнительные сведения см. в спецификации языка C#. Спецификация языка является авторитетным источником синтаксиса и использования языка C#.

См. также