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


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

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

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

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

Замечание

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

Справочные документы по языку C# описывают последнюю выпущенную версию языка C#. Она также содержит начальную документацию по функциям в общедоступных предварительных версиях для предстоящего языкового выпуска.

Документация определяет любую функцию, впервые представленную в последних трех версиях языка или в текущих общедоступных предварительных версиях.

Подсказка

Чтобы узнать, когда функция впервые появилась в C#, ознакомьтесь со статьей об истории версий языка C#.

Примените ключевое слово к полям 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 слово можно применять только к полям или classstruct. Не удается объявить локальные переменные как 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#.

См. также