在 C# 中使用 Queue.Synchronized 或 lock() 来保证线程安全会更好吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/338712/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
In C# would it be better to use Queue.Synchronized or lock() for thread safety?
提问by Jon Tackabury
I have a Queue object that I need to ensure is thread-safe. Would it be better to use a lock object like this:
我有一个 Queue 对象,我需要确保它是线程安全的。使用这样的锁对象会更好吗:
lock(myLockObject)
{
//do stuff with the queue
}
Or is it recommended to use Queue.Synchronized like this:
还是建议像这样使用 Queue.Synchronized:
Queue.Synchronized(myQueue).whatever_i_want_to_do();
From reading the MSDN docs it says I should use Queue.Synchronized to make it thread-safe, but then it gives an example using a lock object. From the MSDN article:
通过阅读 MSDN 文档,它说我应该使用 Queue.Synchronized 使其成为线程安全的,但随后它给出了一个使用锁定对象的示例。来自 MSDN 文章:
To guarantee the thread safety of the Queue, all operations must be done through this wrapper only.
Enumerating through a collection is intrinsically not a thread-safe procedure. Even when a collection is synchronized, other threads can still modify the collection, which causes the enumerator to throw an exception. To guarantee thread safety during enumeration, you can either lock the collection during the entire enumeration or catch the exceptions resulting from changes made by other threads.
为了保证 Queue 的线程安全,所有的操作只能通过这个包装器来完成。
通过集合进行枚举本质上不是线程安全的过程。即使一个集合被同步,其他线程仍然可以修改该集合,这会导致枚举器抛出异常。为了保证枚举期间的线程安全,您可以在整个枚举期间锁定集合或捕获其他线程所做更改导致的异常。
If calling Synchronized() doesn't ensure thread-safety what's the point of it? Am I missing something here?
如果调用 Synchronized() 不能确保线程安全,那有什么意义呢?我在这里错过了什么吗?
采纳答案by Jon Skeet
Personally I always prefer locking. It means that youget to decide the granularity. If you just rely on the Synchronized wrapper, each individual operation is synchronized but if you ever need to do more than one thing (e.g. iterating over the whole collection) you need to lock anyway. In the interests of simplicity, I prefer to just have one thing to remember - lock appropriately!
我个人总是更喜欢锁定。这意味着您可以决定粒度。如果您只依赖 Synchronized 包装器,则每个单独的操作都是同步的,但是如果您需要做不止一件事(例如迭代整个集合),您无论如何都需要锁定。为了简单起见,我更喜欢只记住一件事 - 适当锁定!
EDIT: As noted in comments, if you canuse higher level abstractions, that's great. And if you douse locking, be careful with it - document what you expect to be locked where, and acquire/release locks for as short a period as possible (more for correctness than performance). Avoid calling into unknown code while holding a lock, avoid nested locks etc.
编辑:如评论中所述,如果您可以使用更高级别的抽象,那就太好了。如果您确实使用锁定,请小心使用它 - 记录您希望在哪里锁定的内容,并在尽可能短的时间内获取/释放锁定(更多的是为了正确性而不是性能)。避免在持有锁时调用未知代码,避免嵌套锁等。
In .NET 4 there's a lotmore support for higher-level abstractions (including lock-free code). Either way, I still wouldn't recommend using the synchronized wrappers.
在.NET 4中有一个很大的更高层次的抽象(包括无锁码)更多的支持。无论哪种方式,我仍然不建议使用同步包装器。
回答by Greg Hurlman
It seems clear to me that using a lock(...) {...} lock is the right answer.
我似乎很清楚使用 lock(...) {...} 锁是正确的答案。
To guarantee the thread safety of the Queue, all operations must be done through this wrapper only.
为了保证 Queue 的线程安全,所有的操作只能通过这个包装器来完成。
If other threads access the queue without using .Synchronized(), then you'll be up a creek - unless all your queue access is locked up.
如果其他线程在不使用 .Synchronized() 的情况下访问队列,那么您将陷入困境 - 除非您的所有队列访问都被锁定。
回答by Will Dean
There's frequently a tension between demands for 'thread safe collections' and the requirement to perform multiple operations on the collection in an atomic fashion.
对“线程安全集合”的需求与以原子方式对集合执行多个操作的要求之间经常存在紧张关系。
So Synchronized() gives you a collection which won't smash itself up if multiple threads add items to it simultaneously, but it doesn't magically give you a collection that knows that during an enumeration, nobody else must touch it.
所以 Synchronized() 为您提供了一个集合,如果多个线程同时向其添加项目,该集合不会自行粉碎,但它不会神奇地为您提供一个集合,该集合知道在枚举期间没有其他人必须接触它。
As well as enumeration, common operations like "is this item already in the queue? No, then I'll add it" also require synchronisation which is wider than just the queue.
除了枚举,诸如“这个项目已经在队列中了吗?不,我会添加它”等常见操作也需要比队列更宽的同步。
回答by Greg Beech
There's a major problem with the Synchronized
methods in the old collection library, in that they synchronize at too low a level of granularity (per method rather than per unit-of-work).
Synchronized
旧集合库中的方法存在一个主要问题,因为它们的同步粒度级别太低(每个方法而不是每个工作单元)。
There's a classic race condition with a synchronized queue, shown below where you check the Count
to see if it is safe to dequeue, but then the Dequeue
method throws an exception indicating the queue is empty. This occurs because each individual operation is thread-safe, but the value of Count
can change between when you query it and when you use the value.
有一个带有同步队列的经典竞争条件,如下所示,您可以在其中检查出列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();
}
You can safely write this using a manual lock around the entire unit-of-work (i.e. checking the count anddequeueing the item) as follows:
您可以使用围绕整个工作单元的手动锁定来安全地编写此代码(即检查计数并使项目出列),如下所示:
object item;
lock (queue)
{
if (queue.Count > 0)
{
item = queue.Dequeue();
}
}
So as you can't safely dequeue anything from a synchronized queue, I wouldn't bother with it and would just use manual locking.
因此,由于您无法安全地从同步队列中取出任何内容,因此我不会理会它,只会使用手动锁定。
.NET 4.0 should have a whole bunch of properly implemented thread-safe collections, but that's still nearly a year away unfortunately.
.NET 4.0 应该有一大堆正确实现的线程安全集合,但不幸的是,这仍然是将近一年的时间。
回答by Leonidius
This way we don't need to lock the queue just to find out it was empty.
这样我们就不需要锁定队列只是为了发现它是空的。
object item;
if (queue.Count > 0)
{
lock (queue)
{
if (queue.Count > 0)
{
item = queue.Dequeue();
}
}
}