在C#中,最好使用Queue.Synchronized或lock()来保证线程安全吗?

时间:2008-12-03 21:13:35

标签: c# multithreading queue

我有一个Queue对象,我需要确保它是线程安全的。使用像这样的锁定对象会更好:

lock(myLockObject)
{
//do stuff with the queue
}

或者建议像这样使用Queue.Synchronized:

Queue.Synchronized(myQueue).whatever_i_want_to_do();

通过阅读MSDN文档,它说我应该使用Queue.Synchronized来使其成为线程安全的,但是它给出了一个使用锁对象的示例。来自MSDN文章:

  

保证线程安全   队列,必须完成所有操作   通过这个包装器。

     

通过集合枚举是   本质上不是线程安全的   程序。即使是收藏品   同步,其他线程仍然可以   修改集合,这会导致   枚举器抛出异常。   确保螺纹安全   枚举,你可以锁定   整个收集   枚举或捕获异常   由其他人做出的改变   线程。

如果调用Synchronized()不能确保线程安全有什么意义呢?我在这里错过了什么吗?

5 个答案:

答案 0 :(得分:46)

我个人总是喜欢锁定。这意味着决定粒度。如果您只依赖于Synchronized包装器,则每个单独的操作都会同步,但如果您需要执行多项操作(例如,遍历整个集合),则无论如何都需要锁定。为了简单起见,我更愿意记住一件事 - 适当锁定!

编辑:如评论中所述,如果您可以使用更高级别的抽象,那就太棒了。如果您使用锁定,请小心 - 记录您希望锁定的位置,并在尽可能短的时间内获取/释放锁定(正确性高于性能)。避免在持有锁时调用未知代码,避免嵌套锁等。

在.NET 4中, lot 支持更高级别的抽象(包括无锁代码)。无论哪种方式,我仍然不建议使用同步包装器。

答案 1 :(得分:35)

旧集合库中的Synchronized方法存在一个主要问题,即它们的粒度级别太低(每个方法而不是每个工作单元)。

有一个带有同步队列的经典竞争条件,如下所示,您检查Count以查看是否可以出列,但Dequeue方法会抛出异常,指示队列为空。发生这种情况是因为每个单独的操作都是线程安全的,但Count的值可以在您查询它和使用该值时之间发生变化。

object item;
if (queue.Count > 0)
{
    // at this point another thread dequeues the last item, and then
    // the next line will throw an InvalidOperationException...
    item = queue.Dequeue();
}

您可以使用围绕整个工作单元的手动锁定(即检查计数使项目出列),安全地写入此内容,如下所示:

object item;
lock (queue)
{
    if (queue.Count > 0)
    {
        item = queue.Dequeue();
    }
}

因为你不能安全地从同步队列中出列任何东西,我不会理会它,只会使用手动锁定。

.NET 4.0应该有一大堆正确实现的线程安全集合,但不幸的是,这仍然将近一年之后。

答案 2 :(得分:15)

“线程安全集合”的要求与以原子方式对集合执行多个操作的要求之间经常存在紧张关系。

所以,Synchronized()为你提供了一个集合,如果多个线程同时向它添加项目,它将不会粉碎自己,但它并没有神奇地给你一个集合,它知道在枚举过程中,没有其他人必须触摸它。

除了枚举之外,常见的操作如“这个项目是否已经在队列中?不,那么我将添加它”也需要同步,这比同步更宽。

答案 3 :(得分:7)

这样我们就不需要锁定队列就会发现它是空的。

object item;
if (queue.Count > 0)
{
  lock (queue)
  {
    if (queue.Count > 0)
    {
       item = queue.Dequeue();
    }
  }
}

答案 4 :(得分:1)

我觉得使用锁定(...){...}锁定是正确的答案。

  

为了保证Queue的线程安全,所有操作都必须通过此包装器完成。

如果其他线程在不使用.Synchronized()的情况下访问队列,那么除非所有队列访问都被锁定,否则你将成为一条小溪。