gpt4 book ai didi

c# - 重新排序围绕 volatile 的操作

转载 作者:太空狗 更新时间:2023-10-29 19:42:59 25 4
gpt4 key购买 nike

我目前正在研究写时复制集的实现,并想确认它是线程安全的。我相当确定唯一可能不是这样的方法是允许编译器在某些方法中重新排序语句。例如,Remove 方法如下所示:

public bool Remove(T item)
{
var newHashSet = new HashSet<T>(hashSet);
var removed = newHashSet.Remove(item);
hashSet = newHashSet;
return removed;
}

其中hashSet定义为

private volatile HashSet<T> hashSet;

所以我的问题是,假设 hashSet 是 volatile,这是否意味着新集合上的 Remove 发生在写入成员变量之前?如果不是,则其他线程可能会在删除发生之前看到该集合。

我实际上在生产中没有看到任何问题,但我只是想确认它是安全的。

更新

更具体地说,还有另一种获取IEnumerator的方法:

public IEnumerator<T> GetEnumerator()
{
return hashSet.GetEnumerator();
}

所以更具体的问题是:是否可以保证返回的 IEnumerator 永远不会从删除中抛出 ConcurrentModificationException

更新 2

抱歉,答案都是解决多个作者的线程安全问题。提出了好的观点,但这不是我想在这里找到的。我想知道是否允许编译器将 Remove 中的操作重新排序为如下所示:

    var newHashSet = new HashSet<T>(hashSet);
hashSet = newHashSet; // swapped
var removed = newHashSet.Remove(item); // swapped
return removed;

如果这是可能的,则意味着线程可以在 hashSet 分配之后但在 item 之前调用 GetEnumerator 被移除,这可能导致集合在枚举期间被修改。

Joe Duffy 有一个 blog article指出:

Volatile on loads means ACQUIRE, no more, no less. (There are additional compiler optimization restrictions, of course, like not allowing hoisting outside of loops, but let’s focus on the MM aspects for now.) The standard definition of ACQUIRE is that subsequent memory operations may not move before the ACQUIRE instruction; e.g. given { ld.acq X, ld Y }, the ld Y cannot occur before ld.acq X. However, previous memory operations can certainly move after it; e.g. given { ld X, ld.acq Y }, the ld.acq Y can indeed occur before the ld X. The only processor Microsoft .NET code currently runs on for which this actually occurs is IA64, but this is a notable area where CLR’s MM is weaker than most machines. Next, all stores on .NET are RELEASE (regardless of volatile, i.e. volatile is a no-op in terms of jitted code). The standard definition of RELEASE is that previous memory operations may not move after a RELEASE operation; e.g. given { st X, st.rel Y }, the st.rel Y cannot occur before st X. However, subsequent memory operations can indeed move before it; e.g. given { st.rel X, ld Y }, the ld Y can move before st.rel X.

我读这篇文章的方式是调用 newHashSet.Remove 需要一个 ld newHashSet 并且写入 hashSet 需要一个 st.rel newHashSet。从上面的 RELEASE 定义来看,在存储 RELEASE 之后没有负载可以移动,所以语句不能重新排序!有人可以确认我的解释是正确的吗?

最佳答案

考虑使用 Interlocked.Exchange - 它将保证订购,或 Interlocked.CompareExchange作为附带好处,您可以检测(并可能从中恢复)对集合的同时写入。显然,它增加了一些额外的同步级别,因此它与您当前的代码不同,但更容易推理。

public bool Remove(T item) 
{
var old = hashSet;
var newHashSet = new HashSet<T>(old);
var removed = newHashSet.Remove(item);
var current = Interlocked.CompareExchange(ref hashSet, newHashSet, old);
if (current != old)
{
// failed... throw or retry...
}
return removed;
}

而且我认为在这种情况下您仍然需要 volatile hashSet

关于c# - 重新排序围绕 volatile 的操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11357439/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com