在C#中使用Queue.Synchronized或lock()来保证线程安全性会更好吗?
我有一个Queue对象,我需要确保是线程安全的。 使用这样的锁对象会更好:
lock(myLockObject) { //do stuff with the queue }
还是推荐使用Queue.Synchronized是这样的:
Queue.Synchronized(myQueue).whatever_i_want_to_do();
从阅读MSDN文档它说,我应该使用Queue.Synchronized使其线程安全,但它然后给出了一个使用锁对象的例子。 从MSDN文章:
为了保证队列的线程安全,所有的操作只能通过这个包装来完成。
枚举枚举本质上不是一个线程安全的过程。 即使集合同步,其他线程仍然可以修改集合,这会导致枚举器抛出exception。 为了确保枚举过程中的线程安全性,可以在整个枚举过程中locking集合,或者捕获其他线程所做更改所导致的exception。
如果调用Synchronized()不能确保线程安全,那么它有什么意义呢? 我在这里错过了什么?
就个人而言,我总是喜欢locking。 这意味着你可以决定粒度。 如果你只是依靠Synchronized wrapper,每个单独的操作都是同步的,但是如果你需要做多件事情(比如迭代整个集合),你仍然需要locking。 为了简单起见,我宁愿只记住一件事 – 适当地locking!
编辑:正如在评论中指出的,如果你可以使用更高层次的抽象,那太棒了。 如果你使用locking,要小心 – 记下你希望locking的地方,并获取/释放locking尽可能短的时间(更多的是为了正确性而不是性能)。 避免在持有锁的情况下调用未知的代码,避免嵌套的锁等
在.NET 4中,对更高级的抽象(包括无锁代码)有更多的支持。 无论哪种方式,我仍然不会推荐使用同步包装。
旧集合库中的Synchronized
方法存在一个主要问题,那就是它们的粒度级别太低(每个方法而不是每个工作单元)同步。
有一个经典的竞争条件,带有一个同步队列,下面显示了检查Count
以查看是否可以安全地出Dequeue
队列,但是然后Dequeue
方法抛出一个exception,指示队列是空的。 发生这种情况是因为每个单独的操作都是线程安全的,但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(); } }
所以,你不能安全地从同步队列中取出任何东西,我不会为此而烦恼,只会使用手动locking。
.NET 4.0应该有一大堆正确实现的线程安全的集合,但是遗憾的是这还有将近一年的时间。
“线程安全集合”的需求和以primefaces方式对集合执行多个操作的需求之间经常存在紧张关系。
所以Synchronized()为你提供了一个集合,如果多个线程同时向它添加项目,它不会粉碎自己,但它不会神奇地给你一个集合,它知道在枚举过程中,没有人必须触摸它。
除了枚举之外,像“这个项目已经在队列中了吗?”的常见操作?不,我将添加它“还需要比队列更宽的同步。
这样,我们不需要locking队列就可以发现它是空的。
object item; if (queue.Count > 0) { lock (queue) { if (queue.Count > 0) { item = queue.Dequeue(); } } }
我很清楚,使用锁(…){…}锁是正确的答案。
为了保证队列的线程安全,所有的操作只能通过这个包装来完成。
如果其他线程在不使用.Synchronized()的情况下访问队列,那么你将会陷入困境 – 除非你所有的队列访问都被locking了。