C# ConcurrentDictionary的使用小結(jié)
ConcurrentDictionary<TKey, TValue> 是 .NET 中一個線程安全的字典集合,專為高并發(fā)讀寫場景設(shè)計。它是 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)也能實現(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 一樣,不維護插入順序 |
?? 注意:ConcurrentDictionary 的枚舉(foreach)是線程安全的快照,但可能包含“過時”數(shù)據(jù)(因為其他線程可能正在修改)。
?? 三、常用 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)
如果不存在則添加,存在則更新。
// 實現(xiàn)計數(shù)器
cache.AddOrUpdate("counter",
addValue: 1, // 不存在時設(shè)為1
updateValueFactory: (key, oldValue) => oldValue + 1 // 存在時+1
);
?? 四、與加鎖Dictionary的性能對比
| 場景 | Dictionary + lock | ConcurrentDictionary |
|---|---|---|
| 高并發(fā)讀 | 所有讀需排隊(慢) | 幾乎無鎖(快) |
| 低并發(fā)寫 | 串行寫(中等) | 分段鎖(較快) |
| 高并發(fā)寫 | 嚴(yán)重瓶頸 | 仍優(yōu)于全局鎖 |
| 代碼簡潔性 | 需手動管理鎖 | 無需鎖,API 更豐富 |
?? 在典型 Web 應(yīng)用緩存場景(大量讀 + 少量寫),ConcurrentDictionary 性能可提升 5~10 倍。
?? 五、常見誤區(qū)
? 誤區(qū) 1:認(rèn)為dict[key] = value是原子的
// 錯誤!這實際上是: // if (exists) update; else add; // 但中間可能被其他線程干擾 cache["key"] = newValue; // 不是原子操作!
? 正確做法:
cache.AddOrUpdate("key", newValue, (k, old) => newValue);
? 誤區(qū) 2:在GetOrAdd中做非冪等操作
// 危險!工廠方法可能被多次調(diào)用(雖然最終只存一個結(jié)果)
var obj = cache.GetOrAdd("key", k => new ExpensiveObject()); // OK
// 更危險:有副作用的操作
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.計數(shù)器 / 統(tǒng)計
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 | 初始容量(避免頻繁擴容) |
// 預(yù)期 8 個線程并發(fā)寫,初始存 1000 項
var dict = new ConcurrentDictionary<string, Data>(
concurrencyLevel: 8,
capacity: 1000
);
?? 大多數(shù)場景用默認(rèn)構(gòu)造函數(shù)即可,除非你有明確的性能測試數(shù)據(jù)。
? 總結(jié):何時使用ConcurrentDictionary?
| 場景 | 推薦 |
|---|---|
| 多線程讀寫共享字典 | ? 強烈推薦 |
| 高頻讀 + 低頻寫(如緩存) | ? 最佳選擇 |
| 需要原子“獲取或創(chuàng)建”語義 | ? 必選 |
| 單線程或只讀場景 | ? 用普通 Dictionary 更輕量 |
| 需要保持插入順序 | ? 考慮 ImmutableDictionary 或加鎖的 SortedDictionary |
?? 記?。篊oncurrentDictionary 不是萬能的,但它是在并發(fā)字典場景下最高效、最安全的選擇。
到此這篇關(guān)于C# ConcurrentDictionary的使用小結(jié)的文章就介紹到這了,更多相關(guān)C# ConcurrentDictionary內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#實現(xiàn)XML與實體類之間相互轉(zhuǎn)換的方法(序列化與反序列化)
這篇文章主要介紹了C#實現(xiàn)XML與實體類之間相互轉(zhuǎn)換的方法,涉及C#序列化與反序列化操作的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2016-06-06

