我有一个属性,它的类型是 long (Int64)。
如果它是一个 int,那么我可以将支持字段声明为:
private volatile int _myInt;
并创建一个简单的获取和设置访问器。
但是,C# 编译器不允许使用 long 类型的 volatile 关键字,即使是 x64 项目设置也是如此。所以情况是,即使我们确定读/写操作对该变量是原子的,不幸的是,读取该变量的线程有可能获取并使用旧的(处理器或 CLR/JIT 优化器)缓存值...
问题 1:这是否意味着我必须使用 Interlocked 来防止读取缓存值,而不是简单地在 get 访问器中读取该值?
get
{
return Interlocked.CompareExchange(ref _myLong, 0, 0);
}
这意味着相当大的开销...
问题 2:仍然假设有保证的 64 位架构,在集合访问器中进行简单的赋值是否足够,例如:
set
{
_myLong = value;
}
提前致谢
请您参考如下方法:
您可以使用 System.Threading.Volatile class解决这个问题。例如:
class Example {
private long _prop;
public long prop {
get { return Volatile.Read(ref _prop); }
set { Volatile.Write(ref _prop, value); }
}
}
虽然这看起来效率低下,但实际上它会在 x64 处理器上生成高效代码。 jitter 内置了 Volatile 类的知识,直接将其翻译成机器码,而不是依赖于框架实现。 x64 抖动知道 long 在 64 位 Intel/AMD 处理器上是原子的。例如:
static void Main(string[] args) {
var obj = new Example();
obj.prop = 42;
Console.WriteLine(obj.prop);
}
生成机器码:
00007FFA3DF43AB0 sub rsp,28h ; setup stack frame
00007FFA3DF43AB4 lea rcx,[7FFA3DF959B0h] ; obj = new Example
00007FFA3DF43ABB call 00007FFA9D5A2300
00007FFA3DF43AC0 mov qword ptr [rax+8],2Ah ; obj.prop setter
00007FFA3DF43AC8 mov rcx,qword ptr [rax+8] ; obj.prop getter
00007FFA3DF43ACC call 00007FFA9CD0CFD0 ; Console.WriteLine
00007FFA3DF43AD1 nop ; alignment
00007FFA3DF43AD2 add rsp,28h ; destroy stack frame
00007FFA3DF43AD6 ret ; done
请注意属性 getter 和 setter 是如何被完全消除的,直接访问 Example._prop 字段。这就是你要找的。如果您曾经在内存模型较弱的处理器(如 ARM)上运行此代码,那么它仍会根据此类处理器的要求正确生成适当的获取和释放语义。
