C# 中的队列和等待句柄
我的应用程序中使用以下代码已有多年,但从未发现其中出现问题。
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 或互锁或锁等)。
与队列和 AutoResetEvent 一起使用的常用模式是什么来解决此问题并执行我尝试对上面的代码执行的操作?
(在正确指出 Queue.Count 可以做任何事情及其特定于其实现之后,进行编辑以稍微更改问题)。
I've had the following code in my application for some years and have never seen an issue from it.
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
}
Where CommandEventArr is made up of the NewOrderEvent (an auto reset event) and the ExitEvent (a manual reset event).
But I'm not sure if this is thread safe (assuming N producer threads that all lock the queue before enqueing and one consumer thread that runs the code above). Also, we can assume that the Queue.Count property just returns one instance Int32 value from the Queue class (without volatile or interlocked or a lock, etc.).
What's the usual pattern used with a Queue and an AutoResetEvent to fix this and do what I'm trying to do with the code above?
(Edited to change the question slightly after it was correctly pointed out that Queue.Count could do anything and its implementation specific).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
对我来说看起来相当线程安全,WaitAny() 将立即完成,因为事件已经设置。这不是问题。
不要破坏有效的线程同步。但如果你想要一个更好的捕鼠器,那么你可以考虑 Joe Duffy 的 BlockingQueue 在这个 杂志文章。 .NET 4.0 中提供了更通用的版本,System.Collections.Concurrent .BlockingCollection 和 ConcurrentQueue 作为它的实际实现。
Looks quite thread-safe to me, the WaitAny() will simply complete immediately because thee event is already set. That's not a problem.
Don't break threading sync that works. But if you want a better mousetrap then you could consider Joe Duffy's BlockingQueue in this magazine article. A more general version of it is available in .NET 4.0, System.Collections.Concurrent.BlockingCollection with ConcurrentQueue as a practical implementation of it.
你是对的。该代码不是线程安全的。但不是因为你想的那样。
AutoResetEvent 很好。不过,只是因为您获取了锁并重新测试 PendingOrders.Count。问题的真正关键在于您在锁之外调用 PendingOrders.Count 。因为 Queue 类不是线程安全的,所以您的代码也不是线程安全的...期间。
现在实际上,您可能永远不会遇到这个问题,原因有两个。首先,Queue.Count 属性几乎肯定被设计为永远不会让对象处于半生不熟的状态。毕竟,它可能只会返回一个实例变量。其次,该读取缺乏内存屏障不会对更广泛的代码上下文产生重大影响。最糟糕的情况是,您将在循环的一次迭代中获得陈旧的读取,然后获取的锁将隐式创建内存屏障,并在下一次迭代中发生新的读取。我在这里假设只有一个线程在排队项目。如果有 2 个或更多,情况就会发生很大变化。
不过,让我把这一点说得非常清楚。 您无法保证 PendingOrders.Count 在执行期间不会改变对象的状态。由于它没有被锁住,因此另一个线程可以在仍处于半支持状态时对其发起操作。
You are correct. The code is not thread-safe. But not for the reason you think.
The AutoResetEvent is fine. Though, only because you acquire a lock and retest PendingOrders.Count. The real crux of the matter is that you are calling PendingOrders.Count outside of a lock. Because the Queue class is not thread-safe your code is not thread-safe... period.
Now in reality you will probably never have a problem with this for two reasons. First, Queue.Count property is almost certainly designed to never leave the object in a half-baked state. After all, it will probably just return an instance variable. Second, the lack of a memory barrier on that read will not have a significant impact in the broader context of your code. The worst thing that will happen is that you will get a stale read on one iteration of the loop and then the acquired lock will implicitly create a memory barrier and a fresh read will take place on the next iteration. I am assuming here that there is only one thread queueing items. Things change considerably if there are 2 or more.
However, let me make this perfectly clear. You have no guarantee that PendingOrders.Count will not alter the state of the object during its execution. And because it is not wrapped in a lock, another thread could initiate an operation on it while is still in that half-backed state.
使用手动事件...
然后您还需要确保排队端的锁定:
Using manual events...
Then you need to ensure a lock on the enqueue side as well:
为此,您应该仅使用 WaitAny,并确保在添加到 PendingOrders 集合的每个新订单上都收到信号:
You should only use the WaitAny for that, and ensure that it gets signaled on every new order added to the PendingOrders collection: