Skip to main content
 首页 » 编程设计

c#之在无锁的保证 64 位架构上实现长属性的线程安全原子访问器的最佳方法

2024年02月01日31mengfanrong

我有一个属性,它的类型是 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)上运行此代码,那么它仍会根据此类处理器的要求正确生成适当的获取和释放语义。