为什么将变量声明为 volatile 并同时对其使用 Interlocked?

塔努贾·迪尔汗

我正在阅读 Joe Duffy在 Windows 上并发编程在“Memory Models and Lock Freedom”一章的最后,他给出了一个lock free stack的例子。我已经阅读了代码,但有一点我不明白,那就是需要将m_next字段标记为volatile. 因为有一个完整的内存屏障Interlocked.CompareExchange对吗?有没有人有想法?

我已经粘贴了下面的示例代码。

class LockFreeStack<T>
{
    class Node
    {
        internal T m_value;
        internal volatile Node m_next;
    }

    volatile Node m_head;

    void Push(T value)
    {
        Node n = new Node();
        n.m_value = value;

        Node h;
        do
        {
            h = m_head;
            n.m_next = h;
        }
        while (Interlocked.CompareExchange(ref m_head, n, h) != h);
    }

    T Pop()
    {
        Node n;
        do
        {
            n = m_head;
            if (n == null) throw new Exception('stack empty');

        }
        while (Interlocked.CompareExchange(ref m_head, n.m_next, n) != n);

        return n.m_value;
    }
}
西奥多·祖利亚斯

我会给我的两分钱,但不能 100% 确定我要说的是正确的。Joe Duffy是世界级的多线程专家,但我认为在这个实现中,他对LockFreeStack<T>内部状态的跨线程可见性过于谨慎Node.m_next在我看来,该领域的波动性是多余的。Node实例是可变的,但他们只突变之前,他们在内部链接列表链接。在那个阶段之后,它们实际上是不可变的。该可变阶段由单个线程执行,因此另一个线程不可能看到Node实例的陈旧版本

这使得只有指令的可能性重新排序,为理由,宣布Node.m_nextvolatile由于n.m_next = h;分配夹在读取另一个易失性字段 ( m_head) 和Interlocked.CompareExchange操作之间,我假设已经阻止了可能危及实现正确性的指令重新排序,但正如我所说,我不是 100%当然。

我很确定LockFreeStack<T>该类的实现可以通过使Node不可变来提高性能,但代价是稍微分配更多如今(C# 9)这可以通过从 type 切换到 typeclass实现record这是这样一个实现的样子:

class LockFreeStack<T>
{
    record Node(T Value, Node Next) { }

    Node _head;

    void Push(T value)
    {
        Node h = Volatile.Read(ref _head);
        while (true)
        {
            Node n = new Node(value, h);
            var original = Interlocked.CompareExchange(ref _head, n, h);
            if (original == h) break;
            h = original;
        }
    }

    T Pop()
    {
        Node h = Volatile.Read(ref _head);
        while (true)
        {
            if (h == null) throw new Exception("Stack empty");
            var original = Interlocked.CompareExchange(ref _head, h.Next, h);
            if (original == h) break;
            h = original;
        }
        return h.Value;
    }
}

请注意,每个操作PushPop操作只产生一次波动性成本甚至可以争辩说Volatile.Read,该_head字段的 可以完全省略,因为_head无论如何都会通过第一次Interlocked.CompareExchange操作纠正可能的陈旧,只需要额外的循环迭代while (true)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么在同步块中使用volatile?

为什么AbstractOwnableSynchronizer.exclusiveOwnerThread没有声明为volatile?

为什么在Java中,对象成员变量不能同时为final和volatile?

了解volatile asm与volatile变量

为什么除非我先将toString()声明为变量,否则为什么不能在其上使用toString()?

如何将变量声明为函数并为其赋值

为什么在使用__syncthreads时我们不需要使用volatile变量

为什么局部变量不允许使用volatile关键字?

如何正确使用 Interlocked.Add()?

什么时候不使用Java中的volatile变量

为什么我们使用volatile关键字?

为什么使用volatile会使原子变长和变长

为什么Volatile.Read使用ref参数?

为什么在C ++ 20中不推荐使用volatile?

为什么在双重检查锁定中使用了volatile

为什么volatile不使用std :: min进行编译

当所有变量都声明为double时,为什么System.out.printf仅与%f一起使用?

有什么理由不应该对C中的所有变量和函数声明使用“ volatile”关键字吗?

使用AWSTask,如何将块声明为变量

C ++:使用auto将类声明为函数内的变量

Threading.Volatile.Read(Int64)和Threading.Interlocked.Read(Int64)之间的区别?

为什么一旦将引用声明为const即可使用其他类型的数据?

如何使用 Ansible 遍历主机并同时设置变量

java-我是否必须将共享的侦听器成员变量声明为volatile?

是否必须将在同步块中访问的变量声明为volatile?

Python可以实例化一个变量并同时返回其值或引用吗?

什么是Pytorch中的volatile变量

应该使用可以证明“ volatile”声明的代码示例

为什么允许将通用数组声明为实例变量?