【问题标题】:In C# would it be better to use Queue.Synchronized or lock() for thread safety?在 C# 中使用 Queue.Synchronized 或 lock() 来确保线程安全会更好吗?
【发布时间】:2010-09-25 05:17:19
【问题描述】:

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

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

还是建议这样使用Queue.Synchronized:

Queue.Synchronized(myQueue).whatever_i_want_to_do();

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

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

通过集合枚举是 本质上不是线程安全的 程序。即使是一个集合 同步,其他线程仍然可以 修改集合,这会导致 枚举器抛出异常。 为了保证线程安全 枚举,您可以锁定 在整个收集 枚举或捕获异常 由其他人所做的更改导致 线程。

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

【问题讨论】:

    标签: c# multithreading queue


    【解决方案1】:

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

    编辑:如 cmets 中所述,如果您可以使用更高级别的抽象,那就太好了。如果您确实使用锁定,请小心使用它 - 记录您希望在何处锁定的内容,并在尽可能短的时间内获取/释放锁定(更多的是为了正确性而不是性能)。避免在持有锁时调用未知代码,避免嵌套锁等。

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

    【讨论】:

    • 也许添加一些关于无锁结构的内容?他们不保证有新的答复。
    • @Jonathan:不确定在这种特殊情况下对无锁结构有什么要说的。我通常不会费心去尝试无锁 - 它涉及 很多 令人头疼的问题,而且我不记得 曾经 处于同步瓶颈的情况.
    • @ElazarLeibovich:但是线程安全容器并不是真正意义上的线程安全。它隔离了单个操作,但是当你需要复合操作时怎么办?此外,我经常发现如果我需要对集​​合做某事,我需要对类中的其他字段之一做一些事情。当然,这假设您不打算在 API 中公开集合本身——这将是一个坏主意。
    • @ElazarLeibovich:这取决于队列的用途。您似乎在假设一个发布者/消费者场景。这些天来,我肯定会使用 .NET 4 并发队列来完成 那种 的工作——但请记住,这已经是 2008 年的事了,OP 真的不清楚 使用 这个队列。我坚持使用同步包装器的 not 方法。根据情况,除了锁定之外,可能还有其他方法 - 但同步包装器很少是最好的选择,IMO。
    • @Mathieson:我反对的是你从我的回答转向我鼓励一切一直在锁定下完成的想法。但是,如果您认为我的回答没有帮助,请继续投票并添加您自己的答案。我已经完成了。
    【解决方案2】:

    旧集合库中的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 应该有一大堆正确实现的线程安全集合,但不幸的是,这还有将近一年的时间。

    【讨论】:

    • ConcurrentQueue<T> 现在当然存在——这个问题已经用TryDequeue() 解决了,所以你不能犯这个错误
    【解决方案3】:

    “线程安全集合”的需求与以原子方式对集合执行多个操作的需求之间经常存在矛盾。

    所以 Synchronized() 为您提供了一个集合,如果多个线程同时向其中添加项目,它不会自行破坏,但它不会神奇地为您提供一个知道在枚举期间没有其他人必须触摸它的集合。

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        我似乎很清楚,使用 lock(...) {...} 锁是正确的答案。

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

        如果其他线程在不使用 .Synchronized() 的情况下访问队列,那么您将陷入困境 - 除非您的所有队列访问都被锁定。

        【讨论】:

        • 本文所说的包装器是 Synchronized() 包装器,而不是 lock() 包装器。
        • 正确 - 但在一个线程中使用它不会使其他线程兼容。关键是如果你只依赖 .Synch() ,你会更容易搞砸自己。
        猜你喜欢
        • 2011-06-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-15
        • 1970-01-01
        相关资源
        最近更新 更多