5

多年来,我的应用程序中一直有以下代码,但从未遇到过问题。

while ((PendingOrders.Count > 0) || (WaitHandle.WaitAny(CommandEventArr) != 1))
{
    lock (PendingOrders)
    {
       if (PendingOrders.Count > 0)
       {
           fbo = PendingOrders.Dequeue();
       }
       else
       {
           fbo = null;
       }
    }

    // Do Some Work if fbo is != null
}

其中 CommandEventArr 由 NewOrderEvent(自动重置事件)和 ExitEvent(手动重置事件)组成。

但是我不确定这是否是线程安全的(假设 N 个生产者线程在入队之前都锁定了队列,一个消费者线程运行上面的代码)。此外,我们可以假设 Queue.Count 属性仅从 Queue 类返回一个实例 Int32 值(没有 volatile 或 interlocked 或锁等)。

与 Queue 和 AutoResetEvent 一起使用来解决此问题并使用上面的代码执行我尝试执行的操作的常用模式是什么?

(在正确指出 Queue.Count 可以做任何事情并且它的具体实现之后,稍微改变了这个问题)。

4

4 回答 4

4

对我来说看起来很线程安全,WaitAny() 将立即完成,因为你的事件已经设置。这不是问题。

不要破坏有效的线程同步。但是,如果您想要一个更好的捕鼠器,那么您可以考虑这篇杂志文章中 Joe Duffy 的 BlockingQueue 。它的更通用版本在 .NET 4.0 中可用,System.Collections.Concurrent.BlockingCollection和 ConcurrentQueue 作为它的实际实现。

于 2010-04-30T00:43:47.200 回答
3

你是对的。代码不是线程安全的。但不是你想的那样。

AutoResetEvent 很好。不过,只是因为您获得了锁并重新测试了 PendingOrders.Count。问题的真正症结在于您在锁之外调用 PendingOrders.Count。因为 Queue 类不是线程安全的,所以您的代码不是线程安全的......期间。

现在实际上你可能永远不会有这个问题,原因有两个。首先,几乎可以肯定 Queue.Count 属性被设计为永远不会让对象处于半生不熟的状态。毕竟,它可能只会返回一个实例变量。其次,读取时缺少内存屏障不会对代码的更广泛上下文产生重大影响。将发生的最糟糕的事情是,您将在循环的一次迭代中获得过时的读取,然后获取的锁将隐式创建内存屏障,并且在下一次迭代中将进行新的读取。我在这里假设只有一个线程排队项目。如果有 2 个或更多,情况会发生很大变化。

但是,让我把这一点说清楚。您无法保证 PendingOrders.Count 在执行期间不会改变对象的状态。并且因为它没有包裹在锁中,另一个线程可以在它仍然处于半支持状态时对其发起操作。

于 2010-04-30T02:30:01.740 回答
2

使用手动事件...

ManualResetEvent[] CommandEventArr = new ManualResetEvent[] { NewOrderEvent, ExitEvent };

while ((WaitHandle.WaitAny(CommandEventArr) != 1))
{
    lock (PendingOrders)
    {
        if (PendingOrders.Count > 0)
        {
            fbo = PendingOrders.Dequeue();
        }
        else
        {
            fbo = null;
            NewOrderEvent.Reset();
        }
    }
}

然后,您还需要确保在 enqueue 端锁定:

    lock (PendingOrders)
    {
        PendingOrders.Enqueue(obj);
        NewOrderEvent.Set();
    }
于 2010-04-29T22:24:35.650 回答
0

您应该只为此使用 WaitAny,并确保在添加到 PendingOrders 集合的每个新订单上都收到信号:

while (WaitHandle.WaitAny(CommandEventArr) != 1))
{
   lock (PendingOrders)
   {
      if (PendingOrders.Count > 0)
      {
          fbo = PendingOrders.Dequeue();
      }
      else
      {
          fbo = null;

          //Only if you want to exit when there are no more PendingOrders
          return;
      }
   }

   // Do Some Work if fbo is != null
}
于 2010-04-29T22:22:32.003 回答