use*_*928 5 c# volatile memory-model
在C#.net ConcurrentDictionary(C#参考源)的参考源代码中,我不明白为什么在以下代码片段中需要进行易失性读取:
public bool TryGetValue(TKey key, out TValue value)
{
if (key == null) throw new ArgumentNullException("key");
int bucketNo, lockNoUnused;
// We must capture the m_buckets field in a local variable.
It is set to a new table on each table resize.
Tables tables = m_tables;
IEqualityComparer<TKey> comparer = tables.m_comparer;
GetBucketAndLockNo(comparer.GetHashCode(key),
out bucketNo,
out lockNoUnused,
tables.m_buckets.Length,
tables.m_locks.Length);
// We can get away w/out a lock here.
// The Volatile.Read ensures that the load of the fields of 'n'
//doesn't move before the load from buckets[i].
Node n = Volatile.Read<Node>(ref tables.m_buckets[bucketNo]);
while (n != null)
{
if (comparer.Equals(n.m_key, key))
{
value = n.m_value;
return true;
}
n = n.m_next;
}
value = default(TValue);
return false;
}
Run Code Online (Sandbox Code Playgroud)
评论:
// We can get away w/out a lock here.
// The Volatile.Read ensures that the load of the fields of 'n'
//doesn't move before the load from buckets[i].
Node n = Volatile.Read<Node>(ref tables.m_buckets[bucketNo]);
Run Code Online (Sandbox Code Playgroud)
让我有点困惑。
在从数组中读取变量 n 本身之前,CPU 如何读取 n 的字段?
易失性读取具有获取语义,这意味着它先于其他内存访问。
如果不是易失性读取,则Node我们刚刚获得的下一个字段读取可能会被 JIT 编译器或体系结构推测性地重新排序到读取节点本身之前。
如果这没有意义,请想象一个 JIT 编译器或架构读取将分配给 的任何值n,并开始推测性读取 n.m_key,这样,如果n != null,则不会出现错误预测的分支,不会出现管道泡沫或更糟糕的管道冲洗。
当一条指令的结果可以用作下一条指令的操作数(同时仍在流水线中)时,这是可能的。
对于易失性读取或具有类似获取语义的操作(例如输入锁),C# 规范和 CLI 规范都规定它必须在任何进一步的内存访问之前发生,因此不可能获得未初始化的内存。n.m_key.
也就是说,如果写入也是易失性的或由具有类似释放语义的操作(例如退出锁)保护。
如果没有易失性语义,此类推测性读取可能会返回未初始化的值n.m_key。
同样重要的是由 执行的内存访问comparer。如果节点的对象是在没有易失性释放的情况下初始化的,那么您可能会读取过时的、可能是未初始化的数据。
Volatile.Read这里需要它,因为 C# 本身无法表达对数组元素的易失性读取。m_next读取已声明的字段时不需要它volatile。