thr*_*thr 9 .net c# multithreading volatile interlocked
在SO上有很多关于Interlockedvs 的问题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));
}
// ....
}
Run Code Online (Sandbox Code Playgroud)
Prepend成功之后,线程读取是否head保证获得最新版本,即使它没有标记为volatile?
我一直在做一些实证测试,它似乎工作得很好,我在这里搜索过但没有找到明确的答案(一堆不同的问题和评论/答案都说是冲突的事情).
Interlocked是否保证在CompareExchange操作成功后读取MyObject的任何线程将立即获得新值,或者我是否必须将MyObject标记为volatile来确保这一点?
是的,在同一个线程上的后续读取将获得新值.
你的循环展开到这个:
oldHead = head;
newHead = ... ;
Interlocked.CompareExchange(ref head, newHead, oldHead) // full fence
oldHead = head; // this read cannot move before the fence
Run Code Online (Sandbox Code Playgroud)
编辑:
正常缓存可能发生在其他线程上.考虑:
var copy = head;
while ( copy == head )
{
}
Run Code Online (Sandbox Code Playgroud)
如果您在另一个线程上运行它,编译器可以缓存值,head并且永远不会看到更新.
您的代码应该可以正常工作。尽管没有明确记录该Interlocked.CompareExchange方法将产生全围栏屏障。我想你可以做一个小的改变并省略Object.ReferenceEquals调用,以支持依赖!=默认情况下会执行引用相等的运算符。
值得一提的是,InterlockedCompareExchange Win API调用的文档要好得多。
该函数会生成一个完整的内存屏障(或栅栏),以确保内存操作按顺序完成。
遗憾的是,.NET BCL 对应的Interlocked.CompareExchange上不存在相同级别的文档,因为它们很可能映射到 CAS 的完全相同的底层机制。
现在,在 Prepend 成功后,即使未标记为 volatile,线程读取头是否也能保证获得最新版本?
不,不一定。如果这些线程不生成获取栅栏屏障,则无法保证它们将读取最新值。确保在任何使用head. 您已经确保了在Prepend与Interlocked.CompareExchange通话。当然,该代码可能会以 过时的值遍历一次循环head,但下一次迭代将由于Interlocked操作而刷新。
因此,如果您的问题的上下文与正在执行的其他线程有关,Prepend则无需再执行任何操作。
但是,如果您的问题的上下文与执行另一种方法的其他线程有关,LinkedList那么请确保您使用Thread.VolatileRead或Interlocked.CompareExchange在适当的地方使用。
旁注......可能有可以对以下代码执行的微优化。
newHead = LList<T>.Cons(item, oldHead);
Run Code Online (Sandbox Code Playgroud)
我看到的唯一问题是在循环的每次迭代中都分配了内存。在高争用期间,循环可能会在最终成功之前旋转数次。只要您oldHead在每次迭代中重新分配链接引用(以便您获得新的读取),您就可以将这条线提升到循环之外。这样内存只分配一次。
| 归档时间: |
|
| 查看次数: |
712 次 |
| 最近记录: |