围绕易失性操作的重新排序

Sim*_*onC 8 c# volatile lock-free

我目前正在寻找一个写时复制集实现,并希望确认它的线程安全.我很确定它可能不是唯一的方法是允许编译器在某些方法中重新排序语句.例如,该Remove方法如下所示:

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

其中hashSet定义为

private volatile HashSet<T> hashSet;
Run Code Online (Sandbox Code Playgroud)

所以我的问题是,假设hashSet是volatile,它意味着Remove新集合在写入成员变量之前发生了吗?如果没有,那么其他线程可能会在删除发生之前看到该集合.

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

UPDATE

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

public IEnumerator<T> GetEnumerator()
{
    return hashSet.GetEnumerator();
}
Run Code Online (Sandbox Code Playgroud)

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

更新2

对不起,答案都是针对多个作者的线程安全问题.提出了好点,但这不是我想在这里找到的.我想知道是否允许编译器将操作重新排序为Remove以下内容:

    var newHashSet = new HashSet<T>(hashSet);
    hashSet = newHashSet;                  // swapped
    var removed = newHashSet.Remove(item); // swapped
    return removed;
Run Code Online (Sandbox Code Playgroud)

如果这是可能的,那么这意味着线程可以GetEnumeratorhashSet分配之后调用,但在item删除之前,这可能导致在枚举期间修改集合.

Joe Duffy的博客文章指出:

负载波动意味着ACQUIRE,不多也不少.(当然,有一些额外的编译器优化限制,比如不允许在循环之外提升,但现在让我们关注MM方面.)ACQUIRE的标准定义是后续的内存操作可能不会在ACQUIRE指令之前移动; 例如,给定{ld.acq X,ld Y},ld Y不能在ld.acq X之前出现.但是,之前的内存操作肯定会在它之后移动; 例如,给定{ld X,ld.acq Y},ld.acq Y确实可以在ld X之前出现.目前运行的唯一处理器Microsoft .NET代码实际上是IA64,但这是一个值得注意的领域CLR的MM比大多数机器弱.接下来,.NET上的所有商店都是RELEASE(无论volatile如何,即volatile在jitted代码方面都是无操作的).RELEASE的标准定义是,在RELEASE操作之后,先前的内存操作可能不会移动; 例如,给定{st X,st.rel Y},st.rel Y不能在st X之前出现.但是,后续的存储操作确实可以在它之前移动; 例如,给定{st.rel X,ld Y},ld Y可以在st.rel X之前移动.

我读这个的方式是调用newHashSet.Remove需要a ld newHashSet和写入hashSet需要a st.rel newHashSet.从上面的RELEASE定义中,没有负载可以在商店RELEASE之后移动,因此语句不能重新排序!有人可以确认请确认我的解释是否正确?

Mon*_*mas 1

编辑:

感谢您澄清调用移除(和其他集合突变)时存在外部锁。

hashSet由于 RELEASE 语义,直到为变量分配值之后,您才会最终存储新值(因为之后无法移动)。removedst removedst.rel hashSet

因此, 的“快照”行为GetEnumerator将按预期工作,至少对于以类似方式实现的删除和其他修改器而言是如此。