[C#.NET 拾遗补漏]:理解 volatile 关键字
要理解 C# 中的 volatile
关键字,就要先知道编译器背后的一个基本优化原理。比如对于下面这段代码:
public class Example
{
public int x;
public void DoWork()
{
x = 5;
var y = x + 10;
Debug.WriteLine("x = " +x + ", y = " +y);
}
}
在 Release 模式下,编译器读取 x = 5
后紧接着读取 y = x + 10
,在单线程思维模式下,编译器会认为 y
的值始终都是 15
。所以编译器会把 y = x + 10
优化为 y = 15
,避免每次读取 y
都执行一次 x + 5
。但 x
字段的值可能在运行时被其它的线程修改,我们拿到的 y
值并不是通过最新修改的 x
计算得来的,y
的值永远都是 15
。
也就是说,编译器在 Release 模式下会对字段的访问进行优化,它假定字段都是由单个线程访问的,把与该字段相关的表达式运算结果编译成常量缓存起来,避免每次访问都重复运算。但这样就可能导致其它线程修改了字段值而当前线程却读取不到最新的字段值。为了防止编译器这么做,你就要让编译器用多线程思维去解读代码。告诉编译器字段的值可能会被其它线程修改,这种情况不要使用优化策略。而要做到这一点,就需要使用 volatile
关键字。
给类的字段添加 volatile
关键字,目的是告诉编译器该字段的值可能会被多个独立的线程改变,不要对该字段的访问进行优化。
使用 volatile
可以确保字段的值是可用的最新值,而且该值不会像非 volatile
字段值那样受到缓存的影响。好的做法是将每个可能被多个线程使用的字段标记为 volatile
,以防止非预期的优化行为。
为了加深理解,我们来看一个实际的例子:
public class Worker
{
private bool _shouldStop;
public void DoWork()
{
bool work = false;
// 注意:这里会被编译器优化为 while(true)
while (!_shouldStop)
{
work = !work; // do sth.
}
Console.WriteLine("工作线程:正在终止...");
}
public void RequestStop()
{
_shouldStop = true;
}
}
public class Program
{
public static void Main()
{
var worker = new Worker();
Console.WriteLine("主线程:启动工作线程...");
var workerTask = Task.Run(worker.DoWork);
// 等待 500 毫秒以确保工作线程已在执行
Thread.Sleep(500);
Console.WriteLine("主线程:请求终止工作线程...");
worker.RequestStop();
// 待待工作线程执行结束
workerTask.Wait();
//workerThread.Join();
Console.WriteLine("主线程:工作线程已终止");
}
}
在这个例子中,while (!_shouldStop)
会被编译器优化为 while(true)
。我们可以看一下实际的运行效果来验证这一点。切换 Release 模式,按 Ctrl + F5 运行程序,运行效果始终如下:
程序运行后,虽然主线程在 500 毫秒后执行 RequestStop()
方法修改了 _shouldStop
的值,但工作线程始终都获取不到 _shouldStop
最新的值,也就永远都不会终止 while
循环。
我们修改一下程序,对 _shouldStop
字段加上 volatile
关键字:
public class Worker
{
private volatile bool _shouldStop;
public void DoWork()
{
bool work = false;
// 获取的是最新的 _shouldStop 值
while (!_shouldStop)
{
work = !work; // do sth.
}
Console.WriteLine("工作线程:正在终止...");
}
// ...(略)
}
此时在主线程调用 RequestStop()
方法后,工作线程便立即终止了,运行效果如下图所示:
这说明加了 volatile
关键字后,程序可以实时读取到字段的最新值。
注意,一定要切换为 Release 模式运行才能看到 volatile
发挥的作用,Debug 模式下即使添加了 volatile
关键字,编译器也是不会执行优化的。
当然,并不是所有的类型都可以使用 volatile
关键字修饰的,常见的使用 volatile
的类型是这些简单类型:sbyte, byte, short, ushort, int, uint, char, float 和 bool,其它的请查看参考链接。
参考:
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/volatile
**************转摘:https://www.cnblogs.com/willick/p/13889006.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App