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、ushort、int、或uint。 - 已知为引用类型的泛型类型参数。
- IntPtr 和 UIntPtr。
不能将其他类型的标记,包括 double 和 long,因为 volatile 无法保证读取和写入这些类型的字段是原子的。 若要保护对这些类型的字段的多线程访问,请使用 Interlocked 类成员或使用语句保护访问权限 lock 。
对于大多数多线程方案,即使对于支持的类型来说,也更倾向于使用 Interlocked 操作、lock 语句或其他同步基元,而不是 volatile。 这些替代方法不太容易出现细微的并发错误。
只能将 volatile 关键字应用于或 classstruct. 不能将局部变量声明为 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时,始终获得相同的结果(类似于前面代码中显示的摘录)。 但是,如果< c0 /> 成员没有该修饰符,其行为是不可预测的。 该方法 DoWork 可以优化成员访问,从而读取过时的数据。 由于多线程编程的性质,过时读取次数不可预知。 程序的不同运行会产生一些不同的结果。
C# 语言规范
有关详细信息,请参阅 C# 语言规范。 语言规范是 C# 语法和用法的明确来源。