因此,关于Interlocked
和volatile
的问题相当多,我了解并知道volatile
的概念(无需重新排序,始终从内存中读取等),并且我知道Interlocked
的工作原理原子操作。
但是我的问题是:假设我有一个从多个线程读取的字段,这是某种引用类型,例如:public Object MyObject;
。我知道,如果我像这样进行比较交换:联锁的Interlocked.CompareExchange(ref MyObject, newValue, oldValue)
保证仅将newValue
写入ref MyObject
所指的存储位置,如果ref MyObject
和oldValue
当前指的是同一对象。
但是阅读呢? 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.VolatileRead
或Interlocked.CompareExchange
。
旁注...可能会对以下代码进行微优化。
newHead = LList<T>.Cons(item, oldHead);
我看到的唯一问题是,在循环的每次迭代中都分配了内存。在竞争激烈的时段中,循环可能会旋转几次,直到最终成功。只要您在每次迭代中将链接的引用重新分配给
oldHead
,就可以将这行代码移出循环之外(这样您就可以重新读取内容了)。这样,内存仅分配一次。关于c# - 从多个线程读取/写入的字段,互锁与易失,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8399529/