因此,关于Interlockedvolatile的问题相当多,我了解并知道volatile的概念(无需重新排序,始终从内存中读取等),并且我知道Interlocked的工作原理原子操作。

但是我的问题是:假设我有一个从多个线程读取的字段,这是某种引用类型,例如:public Object MyObject;。我知道,如果我像这样进行比较交换:联锁的Interlocked.CompareExchange(ref MyObject, newValue, oldValue)保证仅将newValue写入ref MyObject所指的存储位置,如果ref MyObjectoldValue当前指的是同一对象。

但是阅读呢? Interlocked是否保证在MyObject操作成功之后读取CompareExchange的任何线程都将立即获得新值,还是必须将MyObject标记为volatile来确保这一点?

我想知道的原因是,我实现了一个无锁的链表,当您向其添加元素时,它会不断更新其内部的“head”节点,如下所示:

[System.Diagnostics.DebuggerDisplay("Length={Length}")]
public class LinkedList<T>
{
    LList<T>.Cell head;

    // ....

    public void Prepend(T item)
    {
        LList<T>.Cell oldHead;
        LList<T>.Cell newHead;

        do
        {
            oldHead = head;
            newHead = LList<T>.Cons(item, oldHead);

        } while (!Object.ReferenceEquals(Interlocked.CompareExchange(ref head, newHead, oldHead), oldHead));
    }

    // ....
}

现在,在Prepend成功之后,即使未将其标记为head,读取volatile的线程是否也可以保证获得最新版本?

我一直在进行一些经验测试,而且似乎工作得很好,我在这里进行了SO搜索,但没有找到明确的答案(一堆不同的问题和评论/答案都说出矛盾的意思)。

最佳答案

您的代码应该可以正常工作。尽管没有明确记录,Interlocked.CompareExchange方法将产生全栅栏屏障。我想您可以做一个小小的更改,并省略Object.ReferenceEquals调用,转而依靠!=运算符,该运算符默认情况下执行引用相等。

值得的是,InterlockedCompareExchange Win API调用的文档要好得多。



遗憾的是,.NET BCL副本Interlocked.CompareExchange上不存在相同级别的文档,因为很有可能它们映射到CAS的完全相同的基础机制。



不,不一定。如果这些线程未生成获取栅栏屏障,则无法保证它们将读取最新值。确保在使用head时执行 volatile 读取。您已经通过Prepend调用确保了Interlocked.CompareExchange中的功能。当然,该代码可能会以过时的head值循环一次,但是由于Interlocked操作,下一次迭代将被刷新。

因此,如果您的问题与其他正在执行Prepend的线程有关,那么就无需执行其他操作了。

但是,如果您的问题是关于在LinkedList上执行另一种方法的其他线程的上下文,请确保在适当的地方使用Thread.VolatileReadInterlocked.CompareExchange

旁注...可能会对以下代码进行微优化。

newHead = LList<T>.Cons(item, oldHead);

我看到的唯一问题是,在循环的每次迭代中都分配了内存。在竞争激烈的时段中,循环可能会旋转几次,直到最终成功。只要您在每次迭代中将链接的引用重新分配给oldHead,就可以将这行代码移出循环之外(这样您就可以重新读取内容了)。这样,内存仅分配一次。

关于c# - 从多个线程读取/写入的字段,互锁与易失,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8399529/

10-10 21:40