在迭代集合时,CAS(比较和交换)线程是否安全?

时间:2014-01-23 13:12:05

标签: c# .net multithreading synchronization locking

(1)在下面的程序中,如果在两个不同的线程上调用Swap和Iterate函数,那么在迭代集合时交换是否安全?

(2)我真的需要互锁交换,因为对引用的读/写是原子的(由CLR保证),假设弱内存模型的发布语义需要Interlocked吗?

class someClass
{
       public void Swap()
       {
           Queue<SomeType> newQueue = new Queue<SomeType>();
           var oldQueue = Interlocked.Exchange(ref queue, newQueue);
           //Perform operation on old queue
       }

       public void Iterate()
       {
           foreach(var someTypeObject in queue)
           {
              //Do Something
           }
        }

       Queue<SomeType> queue = new Queue<Sometype>();
}

2 个答案:

答案 0 :(得分:1)

假设你没有修改oldQueue中的Swap(即在枚举时修改集合),那么是的,这是安全的。您的foreach扩展为:

{
    IEnumerator e = ((IEnumerable)(queue)).GetEnumerator();
    try {
        Sometype someTypeObject;
        while (e.MoveNext()) {
            someTypeObject = (Sometype)e.Current;
            // loop body
       }
    }
    finally {
        // Dispose e
    }
}

来自C# Specification, Section 8.8.4: The foreach statement

换句话说,Iterate中的foreach循环调用queue.GetEnumerator(),然后使用对IEnumerator的{​​{1}}的引用。更改queue指向不再对循环中的枚举器有任何影响。

关于第二个问题,只有在打算使用旧的交换值(并希望它是原子的)时才需要使用queue。否则,如果您只是执行一项任务,则不需要它,因为参考分配始终是原子的(有关更多说明,请参阅this question)。

答案 1 :(得分:1)

这取决于您在oldQueue上执行的操作;如果你在迭代中间交换,迭代仍将在旧队列上,所以如果你对旧队列执行变异操作,这可能会破坏迭代。这应该在另一个方向上是安全的(如果你在交换中间调用Iterate),因为迭代将在新的(空)队列上,尽管它不清楚你想用空做什么队列中。

通常,无锁操作对于性能来说很少是必需的,并且可以弹出许多微妙的问题。你仍然需要小心锁定,但它们比试图用Interlocked做一些聪明的东西更不容易咬你。根据您发布的内容,很难说出适当的锁定设计适用于您的代码,但请考虑可能的边际性能改进是否值得无锁设计的风险和可维护性成本。< / p>