在没有锁的保证 64 位体系结构上实现长属性的线程安全原子访问器的最佳方法

本文关键字:安全 线程 属性 访问 方法 最佳 实现 结构上 | 更新日期: 2023-09-27 18:34:08

我有一个属性,它的类型是长(Int64)。

如果它是一个 int,那么我可以将支持字段声明为:

private volatile int _myInt;

并创建一个简单的获取和设置访问器。

但是,C# 编译器不允许将 volatile 关键字与 long 类型一起使用,即使使用 x64 项目设置也是如此。所以情况是,即使我们确定读/写操作在这个变量上是原子的,不幸的是,存在读取变量的线程将获取并使用旧(处理器或 CLR/JIT 优化器)缓存值的危险......

问题 1:这是否意味着我必须使用互锁来防止读取缓存值,而不是简单地在 get 访问器中读取此值?

get
{
    return Interlocked.CompareExchange(ref _myLong, 0, 0);
}

这意味着相当多的开销...

问题 2:仍然假设一个有保证的 64 位架构,在 set 访问器中进行简单的赋值是否足够,例如:

set
{
    _myLong = value;
}

提前致谢

在没有锁的保证 64 位体系结构上实现长属性的线程安全原子访问器的最佳方法

您可以使用 System.Threading.Volatile 类来解决此问题。 例如:

class Example {
    private long _prop;
    public long prop {
        get { return Volatile.Read(ref _prop); }
        set { Volatile.Write(ref _prop, value); }
    }
}

虽然这看起来效率低下,但实际上在 x64 处理器上生成了高效的代码。 抖动具有 Volatile 类的内置知识,并直接将其转换为机器代码,而不是依赖于框架实现。 x64抖动知道长在64位英特尔/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

并注意属性获取者和二传手是如何被完全消除的,直接访问Example._prop字段。 这就是你要找的。 如果你曾经在具有弱内存模型的处理器(如 ARM)上运行此代码,那么它仍然可以正常工作,并按照此类处理器的要求生成适当的获取和释放语义。