.NET - 字典锁定与 ConcurrentDictionary

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1949131/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 13:42:21  来源:igfitidea点击:

.NET - Dictionary locking vs. ConcurrentDictionary

.netconcurrencydictionaryconcurrentdictionary

提问by TheAJ

I couldn't find enough information on ConcurrentDictionarytypes, so I thought I'd ask about it here.

我找不到关于ConcurrentDictionary类型的足够信息,所以我想我会在这里询问。

Currently, I use a Dictionaryto hold all users that is accessed constantly by multiple threads (from a thread pool, so no exact amount of threads), and it has synchronized access.

目前,我使用 aDictionary来保存由多个线程(来自线程池,因此没有确切数量的线程)不断访问的所有用户,并且它具有同步访问。

I recently found out that there was a set of thread-safe collections in .NET 4.0, and it seems to be very pleasing. I was wondering, what would be the 'more efficient and easier to manage' option, as I have the option between having a normal Dictionarywith synchronized access, or have a ConcurrentDictionarywhich is already thread-safe.

最近发现.NET 4.0中有一套线程安全的集合,看起来很讨喜。我想知道什么是“更高效且更易于管理”的选项,因为我可以选择Dictionary使用同步访问的正常访问,或者已经ConcurrentDictionary是线程安全的。

Reference to .NET 4.0's ConcurrentDictionary

对 .NET 4.0 的引用 ConcurrentDictionary

回答by Lasse V. Karlsen

A thread-safe collection vs. a non-threadsafe-collection can be looked upon in a different way.

可以以不同的方式看待线程安全集合与非线程安全集合。

Consider a store with no clerk, except at checkout. You have a ton of problems if people don't act responsibly. For instance, let's say a customer takes a can from a pyramid-can while a clerk is currently building the pyramid, all hell would break loose. Or, what if two customers reaches for the same item at the same time, who wins? Will there be a fight? This is a non-threadsafe-collection. There's plenty of ways to avoid problems, but they all require some kind of locking, or rather explicit access in some way or another.

考虑一家没有店员的商店,除了在结账时。如果人们不负责任地行事,您就会遇到很多问题。例如,假设客户从金字塔罐中拿走一个罐头,而店员正在建造金字塔,那么一切都会崩溃。或者,如果两个顾客同时到达同一商品,谁会赢?会打架吗?这是一个非线程安全的集合。有很多方法可以避免问题,但它们都需要某种锁定,或者以某种方式进行显式访问。

On the other hand, consider a store with a clerk at a desk, and you can only shop through him. You get in line, and ask him for an item, he brings it back to you, and you go out of the line. If you need multiple items, you can only pick up as many items on each roundtrip as you can remember, but you need to be careful to avoid hogging the clerk, this will anger the other customers in line behind you.

另一方面,考虑在办公桌前有店员的商店,您只能通过他购物。你排队,向他要一件物品,他把它拿回来给你,你就出线了。如果您需要多件物品,您每次往返只能拿起尽可能多的物品,但您需要小心避免霸占店员,这会激怒您身后排队的其他顾客。

Now consider this. In the store with one clerk, what if you get all the way to the front of the line, and ask the clerk "Do you have any toilet paper", and he says "Yes", and then you go "Ok, I'll get back to you when I know how much I need", then by the time you're back at the front of the line, the store can of course be sold out. This scenario is not prevented by a threadsafe collection.

现在考虑这个。在只有一名店员的商店里,如果你一直走到队伍的前面,问店员“你有卫生纸吗”,他说“有”,然后你说“好的,我”当我知道我需要多少时,我会回复你”,然后当你回到队伍的最前面时,商店当然可以卖光了。线程安全集合不会阻止这种情况。

A threadsafe collection guarantees that its internal data structures are valid at all times, even if accessed from multiple threads.

线程安全集合保证其内部数据结构始终有效,即使从多个线程访问也是如此。

A non-threadsafe collection does not come with any such guarantees. For instance, if you add something to a binary tree on one thread, while another thread is busy rebalancing the tree, there's no guarantee the item will be added, or even that the tree is still valid afterwards, it might be corrupt beyond hope.

非线程安全集合没有任何此类保证。例如,如果您在一个线程上向二叉树添加一些内容,而另一个线程正忙于重新平衡树,则无法保证该项目会被添加,或者即使该树之后仍然有效,它也可能被损坏到无法预料的程度。

A threadsafe collection does not, however, guarantee that sequential operations on the thread all work on the same "snapshot" of its internal data structure, which means that if you have code like this:

然而,线程安全集合并不能保证线程上的顺序操作都在其内部数据结构的同一个“快照”上工作,这意味着如果您有这样的代码:

if (tree.Count > 0)
    Debug.WriteLine(tree.First().ToString());

you might get a NullReferenceException because inbetween tree.Countand tree.First(), another thread has cleared out the remaining nodes in the tree, which means First()will return null.

您可能会收到 NullReferenceException,因为在tree.Count和 之间tree.First(),另一个线程清除了树中的剩余节点,这意味着First()将返回null

For this scenario, you either need to see if the collection in question has a safe way to get what you want, perhaps you need to rewrite the code above, or you might need to lock.

对于这种情况,您要么需要查看有问题的集合是否有一种安全的方式来获取您想要的东西,也许您需要重写上面的代码,或者您可能需要锁定。

回答by Mark Byers

You still need to be very careful when using thread-safe collections because thread-safe doesn't mean you can ignore all threading issues. When a collection advertises itself as thread-safe, it usually means that it remains in a consistent state even when multiple threads are reading and writing simultaneously. But that does not mean that a single thread will see a "logical" sequence of results if it calls multiple methods.

在使用线程安全集合时,您仍然需要非常小心,因为线程安全并不意味着您可以忽略所有线程问题。当一个集合将自己标榜为线程安全时,通常意味着即使多个线程同时读取和写入,它也会保持一致状态。但这并不意味着单个线程在调用多个方法时将看到“逻辑”结果序列。

For example, if you first check if a key exists and then later get the value that corresponds to the key, that key may no longer exist even with a ConcurrentDictionary version (because another thread could have removed the key). You still need to use locking in this case (or better: combine the two calls by using TryGetValue).

例如,如果您首先检查某个键是否存在,然后获取与该键对应的值,则即使使用 ConcurrentDictionary 版本,该键也可能不再存在(因为另一个线程可能已删除该键)。在这种情况下,您仍然需要使用锁定(或者更好:使用TryGetValue组合两个调用)。

So do use them, but don't think that it gives you a free pass to ignore all concurrency issues. You still need to be careful.

所以一定要使用它们,但不要认为它可以让您免费忽略所有并发问题。你仍然需要小心。

回答by Stefan Dragnev

Internally ConcurrentDictionary uses a separate lock for each hash bucket. As long as you use only Add/TryGetValue and the like methods that work on single entries, the dictionary will work as an almost lock-free data structure with the respective sweet performance benefit. OTOH the enumeration methods (including the Count property) lock all buckets at once and are therefore worse than a synchronized Dictionary, performance-wise.

ConcurrentDictionary 在内部为每个哈希桶使用单独的锁。只要您只使用 Add/TryGetValue 和处理单个条目的类似方法,字典就会作为一个几乎无锁的数据结构工作,并具有各自的甜蜜性能优势。OTOH 枚举方法(包括 Count 属性)一次锁定所有存储桶,因此在性能方面比同步字典更糟糕。

I'd say, just use ConcurrentDictionary.

我想说,只需使用 ConcurrentDictionary。

回答by Konstantin

I think that ConcurrentDictionary.GetOrAdd method is exactly what most multi-threaded scenarios need.

我认为 ConcurrentDictionary.GetOrAdd 方法正是大多数多线程场景所需要的。

回答by scope_creep

Have you seen the Reactive Extensionsfor .Net 3.5sp1. According to Jon Skeet, they have backported a bundle of the parallel extensions and concurrent data structures for .Net3.5 sp1.

你看过.Net 3.5sp1的Reactive Extensions吗?根据 Jon Skeet 的说法,他们已经为 .Net3.5 sp1 反向移植了一组并行扩展和并发数据结构。

There is a set of samples for .Net 4 Beta 2, which describes in pretty good detail on how to use them the parallel extensions.

.Net 4 Beta 2 有一组示例,它非常详细地描述了如何使用并行扩展。

I've just spent the last week testing the ConcurrentDictionary using 32 threads to perform I/O. It seems to work as advertised, which would indicate a tremendous amount of testing has been put into it.

上周我刚刚使用 32 个线程来测试 ConcurrentDictionary 来执行 I/O。它似乎像宣传的那样工作,这表明已经进行了大量的测试。

Edit: .NET 4 ConcurrentDictionary and patterns.

编辑:.NET 4 ConcurrentDictionary 和模式。

Microsoft have released a pdf called Patterns of Paralell Programming. Its reallly worth downloading as it described in really nice details the right patterns to use for .Net 4 Concurrent extensions and the anti patterns to avoid. Here it is.

微软发布了一个名为 Patterns of Parallell Programming 的 pdf。它真的值得下载,因为它非常详细地描述了用于 .Net 4 并发扩展的正确模式以及要避免的反模式。这里是。

回答by ChaosPandion

Basically you want to go with the new ConcurrentDictionary. Right out of the box you have to write less code to make thread safe programs.

基本上你想使用新的 ConcurrentDictionary。开箱即用,您必须编写更少的代码来制作线程安全的程序。

回答by Theodor Zoulias

The ConcurrentDictionaryis a great option if it fulfills allyour thread-safety needs. If it's not, in other words of you are doing anything slightly complex, a normal Dictionary+lockmay be a better option. For example lets say that you are adding some orders into a dictionary, and you want to keep updated the total amount of the orders. You may write code like this:

ConcurrentDictionary如果它满足您所有的线程安全需求,这是一个很好的选择。如果不是,换句话说,您正在做一些稍微复杂的事情,正常的Dictionary+lock可能是更好的选择。例如,假设您将一些订单添加到字典中,并且您希望保持更新订单的总量。你可以这样写代码:

private ConcurrentDictionary<int, Order> _dict;
private Decimal _totalAmount = 0;

while (await enumerator.MoveNextAsync())
{
    Order order = enumerator.Current;
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

This code is not thread safe. Multiple threads updating the _totalAmountfield may leave it in a corrupted state. So you may try to protect it with a lock:

此代码不是线程安全的。更新该_totalAmount字段的多个线程可能会使其处于损坏状态。因此,您可以尝试使用以下方法保护它lock

_dict.TryAdd(order.Id, order);
lock (_locker) _totalAmount += order.Amount;

This code is "safer", but still not thread safe. There is no guarantee that the _totalAmountis consistent with the entries in the dictionary. Another thread may try to read these values, to update a UI element:

这段代码“更安全”,但仍然不是线程安全的。不能保证_totalAmount与字典中的条目一致。另一个线程可能会尝试读取这些值,以更新 UI 元素:

Decimal totalAmount;
lock (_locker) totalAmount = _totalAmount;
UpdateUI(_dict.Count, totalAmount);

The totalAmountmay not correspond to the count of orders in the dictionary. The displayed statistics could be wrong. At this point you will realize that you must extend the lockprotection to include the updating of the dictionary:

totalAmount未必对应的订单在字典中的计数。显示的统计数据可能有误。此时您将意识到您必须扩展lock保护以包括字典的更新:

// Thread A
lock (_locker)
{
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

// Thread B
int ordersCount;
Decimal totalAmount;
lock (_locker)
{
    ordersCount = _dict.Count;
    totalAmount = _totalAmount;
}
UpdateUI(ordersCount, totalAmount);

This code is perfectly safe, but all the benefits of using a ConcurrentDictionaryare gone.

这段代码是完全安全的,但是使用 a 的所有好处ConcurrentDictionary都没有了。

  1. The performance has become worse than using a normal Dictionary, because the internal locking inside the ConcurrentDictionaryis now wasteful and redundant.
  2. You must review all your code for unprotected uses of the shared variables.
  3. You are stuck with using an awkward API (TryAdd?, AddOrUpdate?).
  1. 性能变得比使用普通 更差Dictionary,因为内部锁定ConcurrentDictionary现在是浪费和多余的。
  2. 您必须检查所有代码以了解共享变量的未受保护使用。
  3. 您一直在使用笨拙的 API(TryAdd?,AddOrUpdate?)。

So my advice is: start with a Dictionary+lock, and keep the option of upgrading later to a ConcurrentDictionaryas a performance optimization, if this option is actually viable. In many cases it will not be.

所以我的建议是:从Dictionary+开始lock,并保留稍后升级到 aConcurrentDictionary作为性能优化的选项,如果这个选项实际上可行的话。在许多情况下,它不会。

回答by Michael Freidgeim

We've used?ConcurrentDictionary for cached collection, that is re-populated every 1 hour and then read by multiple client threads, similar to the solutionfor the Is this example thread safe?question.

我们使用?ConcurrentDictionary缓存的集合,也就是再填充每隔1小时,然后通过多个客户端线程读取,类似的解决方案是这个示例线程安全吗?题。

We found, that changing it to?ReadOnlyDictionary?improved overall performance.

我们发现,把它改成?ReadOnlyDictionary?提高了整体性能。