前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[C#]线程安全的字典ConcurrentDictionary

[C#]线程安全的字典ConcurrentDictionary

作者头像
科控物联
发布2022-03-29 16:26:02
7.4K0
发布2022-03-29 16:26:02
举报
文章被收录于专栏:科控自动化

问题

假设现在有一个键– 值对集合需要保持同步,比如内存缓存,不过有多个线程正在对其执行读写操作。

解决方案

.NET 框架中的 ConcurrentDictionary<TKey, TValue> 类型就是数据结构中的宝藏。它是线程安全的,混用细粒度锁和无锁技术,确保能在大多数场景中快速访问。另外,它的 API 需要花些功夫来熟悉。它必须处理来自多个线程的并发访问,这一点与标准的 Dictionary<TKey, TValue> 类型非常不同。但是,一旦学会了本节中的基础知识,就会发现 ConcurrentDictionary<TKey, TValue> 是非常实用的集合类型。

首先来看如何对集合写入值。可以通过 AddOrUpdate 实现给键赋值:var dictionary = new ConcurrentDictionary<int, string>();

string newValue = dictionary.AddOrUpdate(0,

key => "Zero",

(key, oldValue) => "Zero");AddOrUpdate 有一些复杂,它要根据并发字典当前的内容处理若干件事情。第 1 个参数是键,第 2 个参数是委托,通过委托将键(本例中为 0)转换为待添加至字典的值(本例中为“Zero”)。只有当字典中不存在该键时,才会调用该委托。第 3 个参数是另一个委托,它把键(0)和旧值转换为已更新的、待存入字典的值(“Zero”)。同样,只有当字典中不存在该键时,才会调用该委托。AddOrUpdate 会为该键返回新值,这个新值与任意委托返回的值一样。

接下来才是真正复杂的部分:为了能让并发字典稳妥地工作,AddOrUpdate 可能需要多次调用任意委托,或同时调用两个委托。这非常罕见,却是有可能发生的。因此,委托应该简单且迅捷,并且不会产生副作用。这意味着委托应该只创建值,而不改变应用程序中的任意其他变量。所有传入 ConcurrentDictionary<TKey, TValue> 的方法的委托,都同样遵循该原则。

还有若干种方法可以向字典中添加值,使用索引语句就是一种快捷方法:// 使用与前面相同的“字典”

// 添加(或更新)0键,赋值为"Zero"

dictionary[0] = "Zero";

索引语句的功能没那么强大,不能通过它基于现有值来更新一个值。然而,若有需要存入字典的值,这种语句就更为简单易用。

下面来看一下如何读取值。通过 TryGetValue 便很容易实现:// 使用与前面相同的“字典”

bool keyExists = dictionary.TryGetValue(0, out string currentValue);

如果在字典中找到 out 键,TryGetValue 就会返回 true,并且会给它赋值。相反,如果没有找到 out 键,TryGetValue 就会返回 false。也可以使用索引语句来读取值,但那种做法并不实用,这是因为它会在找不到键的情况下抛出异常。特别注意,并发字典有多个线程在读取、更新、添加和移除值,而且在许多情况下,在尝试读取某个键之前,根本无法知晓这个键是否存在。

移除值与读取值一样容易操作:// 使用与前面相同的“字典”

bool keyExisted = dictionary.TryRemove(0, out string removedValue);TryRemove 与 TryGetValue 几乎一致,唯一不同之处就是如果在字典中找到键,那么它会将键 –值对移除。

讨论

虽然 ConcurrentDictionary<TKey, TValue> 是线程安全的,但这并不意味着它是原子操作。如果两个线程并发调用 AddOrUpdate,那么两者可能都会检测到键的缺失,同时并发执行各自的委托来创建新值。ConcurrentDictionary<TKey, TValue> 很实用,这主要是因为有强大的 AddOrUpdate 方法。然而,它并非适用于所有情况。当有多个线程读写共享集合时,最好使用 ConcurrentDictionary<TKey, TValue>。但是,如果更新并非常态(相对较少),那么或许 ImmutableDictionary<TKey, TValue> 才是更好的选择。ConcurrentDictionary<TKey, TValue> 最适用于共享数据的情况,在这种情况下,多个线程共享相同的集合。如果某些线程只添加元素,而其他线程只移除元素,那么最好使用生产者集合(或消费者集合)。ConcurrentDictionary<TKey, TValue> 并非唯一的线程安全集合,BCL 也提供了 ConcurrentStack<T>、ConcurrentQueue<T> 以及 ConcurrentBag<T>。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 科控物联 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档