C# ConcurrentDictionary的使用小結(jié)
ConcurrentDictionary<TKey, TValue> 是 .NET 中一個線程安全的字典集合,專為高并發(fā)讀寫場景設(shè)計(jì)。它是 System.Collections.Concurrent 命名空間下的核心類型之一,適用于多線程環(huán)境中需要高效、安全地共享鍵值對數(shù)據(jù)的場景。
? 一、為什么需要ConcurrentDictionary?
普通 Dictionary<TKey, TValue> 不是線程安全的。如果多個線程同時讀寫:
var dict = new Dictionary<string, int>(); // 線程A: dict["a"] = 1; // 線程B: dict["b"] = 2; // 可能拋出 InvalidOperationException 或數(shù)據(jù)損壞!
即使加鎖(lock)也能實(shí)現(xiàn)線程安全,但會帶來性能瓶頸(串行化訪問)。
而 ConcurrentDictionary:
- 無需外部加鎖
- 內(nèi)部使用細(xì)粒度鎖或無鎖算法
- 支持高并發(fā)讀 + 適度并發(fā)寫
?? 二、核心特性
| 特性 | 說明 |
|---|---|
| 線程安全 | 所有公共成員(Add、Get、Remove 等)都是線程安全的 |
| 高性能并發(fā)讀 | 讀操作幾乎無鎖(lock-free),性能接近普通字典 |
| 分段/桶式結(jié)構(gòu) | 內(nèi)部將數(shù)據(jù)分片(buckets),減少寫沖突 |
| 原子操作支持 | 提供 AddOrUpdate, GetOrAdd 等復(fù)合原子操作 |
| 不保證順序 | 和 Dictionary 一樣,不維護(hù)插入順序 |
?? 注意:ConcurrentDictionary 的枚舉(foreach)是線程安全的快照,但可能包含“過時”數(shù)據(jù)(因?yàn)槠渌€程可能正在修改)。
?? 三、常用 API 與示例
1. 創(chuàng)建
var cache = new ConcurrentDictionary<string, int>(); // 或指定初始容量和并發(fā)級別(高級用法) var cache2 = new ConcurrentDictionary<string, int>(concurrencyLevel: 4, capacity: 16);
2. 基本操作(線程安全)
// 添加(如果不存在)
cache.TryAdd("key1", 100); // 返回 bool
// 獲?。ㄈ绻嬖冢?
if (cache.TryGetValue("key1", out int value))
{
Console.WriteLine(value); // 100
}
// 更新(如果存在)
cache.TryUpdate("key1", 200, 100); // 僅當(dāng)當(dāng)前值為100時更新為200
// 刪除
cache.TryRemove("key1", out int removedValue);
3. 高級原子操作(? 最常用?。?/h3>
?GetOrAdd(key, valueFactory)
如果 key 不存在,則調(diào)用工廠方法創(chuàng)建值并添加;否則返回現(xiàn)有值。
var config = cache.GetOrAdd("config", key =>
{
// 模擬耗時加載(只執(zhí)行一次!)
Thread.Sleep(1000);
return LoadConfigFromDatabase();
});
?? 多個線程同時調(diào)用 GetOrAdd("config", ...) 時,工廠方法只會被調(diào)用一次(其他線程等待結(jié)果),避免重復(fù)初始化!
?AddOrUpdate(key, addValueFactory, updateValueFactory)
如果不存在則添加,存在則更新。
// 實(shí)現(xiàn)計(jì)數(shù)器
cache.AddOrUpdate("counter",
addValue: 1, // 不存在時設(shè)為1
updateValueFactory: (key, oldValue) => oldValue + 1 // 存在時+1
);
?? 四、與加鎖Dictionary的性能對比
| 場景 | Dictionary + lock | ConcurrentDictionary |
|---|---|---|
| 高并發(fā)讀 | 所有讀需排隊(duì)(慢) | 幾乎無鎖(快) |
| 低并發(fā)寫 | 串行寫(中等) | 分段鎖(較快) |
| 高并發(fā)寫 | 嚴(yán)重瓶頸 | 仍優(yōu)于全局鎖 |
| 代碼簡潔性 | 需手動管理鎖 | 無需鎖,API 更豐富 |
?? 在典型 Web 應(yīng)用緩存場景(大量讀 + 少量寫),ConcurrentDictionary 性能可提升 5~10 倍。
?? 五、常見誤區(qū)
? 誤區(qū) 1:認(rèn)為dict[key] = value是原子的
// 錯誤!這實(shí)際上是: // if (exists) update; else add; // 但中間可能被其他線程干擾 cache["key"] = newValue; // 不是原子操作!
? 正確做法:
cache.AddOrUpdate("key", newValue, (k, old) => newValue);
? 誤區(qū) 2:在GetOrAdd中做非冪等操作
// 危險(xiǎn)!工廠方法可能被多次調(diào)用(雖然最終只存一個結(jié)果)
var obj = cache.GetOrAdd("key", k => new ExpensiveObject()); // OK
// 更危險(xiǎn):有副作用的操作
cache.GetOrAdd("key", k =>
{
Log("Creating instance"); // 可能被記錄多次!
return new MyService();
});
?? 雖然最終值是唯一的,但工廠方法可能被多個線程同時調(diào)用(.NET 6+ 已優(yōu)化為單次調(diào)用,但舊版本不一定)。建議工廠方法無副作用、冪等。
?? 六、典型應(yīng)用場景
1.內(nèi)存緩存(Cache)
public class InMemoryCache
{
private readonly ConcurrentDictionary<string, object> _cache = new();
public T GetOrCreate<T>(string key, Func<T> factory)
{
return (T)_cache.GetOrAdd(key, k => factory());
}
}
2.計(jì)數(shù)器 / 統(tǒng)計(jì)
private readonly ConcurrentDictionary<string, int> _hitCounts = new();
public void RecordHit(string page)
{
_hitCounts.AddOrUpdate(page, 1, (k, v) => v + 1);
}
3.對象池(Object Pool)
private readonly ConcurrentDictionary<Type, Stack<object>> _pools = new();
public object Rent(Type type)
{
var pool = _pools.GetOrAdd(type, t => new Stack<object>());
return pool.TryPop(out var obj) ? obj : Activator.CreateInstance(type);
}
?? 七、性能調(diào)優(yōu)建議
| 參數(shù) | 說明 |
|---|---|
| concurrencyLevel | 預(yù)期并發(fā)更新線程數(shù)(默認(rèn)為 CPU 核心數(shù)) |
| capacity | 初始容量(避免頻繁擴(kuò)容) |
// 預(yù)期 8 個線程并發(fā)寫,初始存 1000 項(xiàng)
var dict = new ConcurrentDictionary<string, Data>(
concurrencyLevel: 8,
capacity: 1000
);
?? 大多數(shù)場景用默認(rèn)構(gòu)造函數(shù)即可,除非你有明確的性能測試數(shù)據(jù)。
? 總結(jié):何時使用ConcurrentDictionary?
| 場景 | 推薦 |
|---|---|
| 多線程讀寫共享字典 | ? 強(qiáng)烈推薦 |
| 高頻讀 + 低頻寫(如緩存) | ? 最佳選擇 |
| 需要原子“獲取或創(chuàng)建”語義 | ? 必選 |
| 單線程或只讀場景 | ? 用普通 Dictionary 更輕量 |
| 需要保持插入順序 | ? 考慮 ImmutableDictionary 或加鎖的 SortedDictionary |
?? 記?。篊oncurrentDictionary 不是萬能的,但它是在并發(fā)字典場景下最高效、最安全的選擇。
到此這篇關(guān)于C# ConcurrentDictionary的使用小結(jié)的文章就介紹到這了,更多相關(guān)C# ConcurrentDictionary內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#利用DesignSurface如何實(shí)現(xiàn)簡單的窗體設(shè)計(jì)器
這篇文章主要介紹了C#利用DesignSurface如何實(shí)現(xiàn)簡單窗體設(shè)計(jì)器的相關(guān)資料,文中通過圖文及示例代碼介紹的很詳細(xì),對大家具有一定的參考價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2017-02-02
c#使用windows服務(wù)更新站點(diǎn)地圖的詳細(xì)示例
這篇文章主要介紹了c#使用windows服務(wù)更新站點(diǎn)地圖的詳細(xì)示例,需要的朋友可以參考下2014-04-04
C#使用Lazy<T>實(shí)現(xiàn)對客戶訂單的延遲加載
這篇文章介紹了C#使用Lazy<T>實(shí)現(xiàn)對客戶訂單延遲加載的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08
C# 中的動態(tài)創(chuàng)建組件(屬性及事件)的實(shí)現(xiàn)思路及方法
這篇文章主要介紹了C# 中的動態(tài)創(chuàng)建組件,有需要的朋友可以參考一下2013-12-12
C#使用Pipelines實(shí)現(xiàn)處理Socket數(shù)據(jù)包
這篇文章主要為大家詳細(xì)介紹了C#如何使用Pipelines實(shí)現(xiàn)處理Socket數(shù)據(jù)包,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12

