关键字 volatile
指示字段可能由同时执行的多个线程修改。 出于性能原因,编译器、运行时系统甚至硬件可能会重新排列对内存位置的读取和写入。 声明 volatile
的字段从某些类型的优化中排除。 不确保从所有执行线程整体来看时所有易失性写入操作均按执行顺序排序。 有关更多信息,请参见 Volatile 类。
谨慎
关键字 volatile
在多线程编程中经常被误解和滥用。 在大多数情况下,您应该使用更安全、更可靠的替代方法,而不是 volatile
。 新式 .NET 提供了更好的并发工具,例如 Interlocked 类、 lock
语句或更高级别的同步基元。 这些替代方法比 volatile
提供更清晰的语义和更强的保证。 请考虑仅在极少见的高级方案中使用 volatile
,其中你完全了解其限制,并已验证它是适当的解决方案。
注释
在多处理器系统上,易失性读取操作不保证获取由任何处理器写入到该内存位置的最新值。 同样,易失性写入操作不保证写入的值将立即对其他处理器可见。
可以将 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
只能应用于 class
或 struct
的字段。 无法声明 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# 语法和用法的明确来源。