我找不到足够的信息 ConcurrentDictionary 类型,所以我想我应该在这里问一下。

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

最近发现.NET 4.0中有一套线程安全的集合,看起来很赏心悦目。我想知道“更高效、更容易管理”的选项是什么,因为我可以选择正常的 Dictionary 具有同步访问,或者具有 ConcurrentDictionary 这已经是线程安全的了。

参考.NET 4.0 ConcurrentDictionary

有帮助吗?

解决方案

一个线程集合与一个非线程收集可根据不同的方式来看待。

考虑一个商店没有店员,除了在付款处。你有一吨重的问题,如果人们不采取负责任的行动。举例来说,假设一个客户需要一个可以从金字塔可以在一个店员正在建造金字塔,所有的地狱将打破松散。或者,如果两个客户在同一时间到达同一项目,谁赢了?会不会有打架?这是一个非线程安全的集合。有很多方法可以规避的问题,但它们都需要某种形式的锁定,或以某种方式或其他相当明确的访问的。

在另一方面,考虑一个店员在店内一张桌子,你只能通过他的店。你得到的线,并要求他的项目,他把它带回给你,你出去行。如果您需要多个项目,你只能拿起尽可能多的项目在每个往返你能记住,但你必须要小心,以避免占用店员,这会激怒其他客户在你后面线。

现在考虑这一点。在同一个店员,如果你得到了什么一直到该行的前面,并询问店员“你有什么卫生纸”,他说“是”,然后在商店,你去“好吧,我会回来给你当我知道我是多么需要”,然后你回到了队伍的前面的时候,店里当然可以销售一空。这种情况下不是由线程集合防止。

一个线程集合保证其内部数据结构是在任何时候都有效,即使从多个线程访问。

一个非线程集合不附带任何这种保证。举例来说,如果你添加了一些二叉树的一个线程,而另一个线程正忙于重新平衡树,也不能保证该项目将被添加,甚至认为树仍然有效之后,它可能已损坏超越的希望。

一个线程收集不,但是,保证顺序操作的线程上的所有操作相同的内部数据结构,这意味着“快照”,如果你有这样的代码:

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

由于其间tree.Counttree.First(),另一个线程已清理出的树,这意味着First()将返回null其余节点你可能会得到一个NullReferenceException。

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

其他提示

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

例如,如果您首先检查是否存在的关键和后来获得对应于该键的值,该键可以甚至不再有ConcurrentDictionary版本存在(因为另一个线程可能已经删除了键)。你仍然需要使用在这种情况下锁定(或更好的:使用的 TryGetValue )。

所以,做使用它们,但不要以为它给你的免费通行证忽略所有并发问题。你仍然需要小心。

内部ConcurrentDictionary使用每个散列桶的单独的锁。只要您仅使用添加/ TryGetValue和单项工作等的方法,词典将作为与各甜美的性能优势几乎无锁的数据结构。 OTOH枚举方法(包括Count属性)锁定所有桶一次,因此比同步字典恶化,性能,明智的。

我说,只要使用ConcurrentDictionary。

我觉得ConcurrentDictionary.GetOrAdd方法正是最多线程情况下所需要的。

你见过无扩展对于.NET 3.5SP1。据乔恩斯基特,他们已经向后移植的并行扩展和并发数据结构的束为.NET3.5 SP1。

有用于净4 Beta 2中,其描述了关于如何使用它们的并行扩展不错细节一组样品。

我刚度过的上周使用32个线程来执行I / O测试ConcurrentDictionary。它似乎工作作为标榜,这表明测试的大量已投入它。

修改:.NET 4 ConcurrentDictionary和模式

微软已经发布Paralell编程的PDF称为模式。它reallly值得下载,因为它在非常好的详细地描述了合适的模式来使用.NET 4个的并行扩展和反模式避免。 这。

基本上你想要去与新ConcurrentDictionary。开箱,你必须写更少的代码,使线程安全的方案。

我们已经使用ConcurrentDictionary缓存的集合,即重新填充每隔1小时,然后多个客户线程,类似阅读对于解决是这个示例线程安全的吗?问题。

我们发现,改变它 ReadOnlyDictionary 改善的总体性能

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;
}

这段代码不是线程安全的。多线程更新 _totalAmount 字段可能会使其处于损坏状态。所以你可以尝试用一个来保护它 lock:

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

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

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

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);

这段代码是完全安全的,但是使用 ConcurrentDictionary 消失了。

  1. 性能变得比使用正常的差 Dictionary, ,因为内部锁定 ConcurrentDictionary 现在是浪费和多余的。
  2. 您必须检查所有代码,以了解共享变量的不受保护的使用情况。
  3. 你被困于使用一个尴尬的 API (TryAdd?, AddOrUpdate?).

所以我的建议是:从一个开始 Dictionary+lock, ,并保留稍后升级到 ConcurrentDictionary 作为一种性能优化,如果这个选项确实可行的话。在许多情况下,情况并非如此。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top