C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C# ConcurrentDictionary

C# ConcurrentDictionary的使用小结

作者:她说彩礼65万

ConcurrentDictionary是.NET中用于高并发读写的线程安全字典,适用于多线程环境,本文就来介绍一下C# ConcurrentDictionary的使用小结,感兴趣的可以了解一下

ConcurrentDictionary<TKey, TValue> 是 .NET 中一个线程安全的字典集合,专为高并发读写场景设计。它是 System.Collections.Concurrent 命名空间下的核心类型之一,适用于多线程环境中需要高效、安全地共享键值对数据的场景。

✅ 一、为什么需要ConcurrentDictionary?

普通 Dictionary<TKey, TValue> 不是线程安全的。如果多个线程同时读写:

var dict = new Dictionary<string, int>();
// 线程A: dict["a"] = 1;
// 线程B: dict["b"] = 2;
// 可能抛出 InvalidOperationException 或数据损坏!

即使加锁(lock)也能实现线程安全,但会带来性能瓶颈(串行化访问)。

ConcurrentDictionary

🧱 二、核心特性

特性说明
线程安全所有公共成员(Add、Get、Remove 等)都是线程安全的
高性能并发读读操作几乎无锁(lock-free),性能接近普通字典
分段/桶式结构内部将数据分片(buckets),减少写冲突
原子操作支持提供 AddOrUpdate, GetOrAdd 等复合原子操作
不保证顺序Dictionary 一样,不维护插入顺序

⚠️ 注意:ConcurrentDictionary 的枚举(foreach)是线程安全的快照,但可能包含“过时”数据(因为其他线程可能正在修改)。

🔧 三、常用 API 与示例

1. 创建

var cache = new ConcurrentDictionary<string, int>();
// 或指定初始容量和并发级别(高级用法)
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); // 仅当当前值为100时更新为200

// 删除
cache.TryRemove("key1", out int removedValue);

3. 高级原子操作(⭐ 最常用!)

✅GetOrAdd(key, valueFactory)

如果 key 不存在,则调用工厂方法创建值并添加;否则返回现有值。

var config = cache.GetOrAdd("config", key =>
{
    // 模拟耗时加载(只执行一次!)
    Thread.Sleep(1000);
    return LoadConfigFromDatabase();
});

💡 多个线程同时调用 GetOrAdd("config", ...) 时,工厂方法只会被调用一次(其他线程等待结果),避免重复初始化!

✅AddOrUpdate(key, addValueFactory, updateValueFactory)

如果不存在则添加,存在则更新。

// 实现计数器
cache.AddOrUpdate("counter", 
    addValue: 1,                          // 不存在时设为1
    updateValueFactory: (key, oldValue) => oldValue + 1 // 存在时+1
);

⚖️ 四、与加锁Dictionary的性能对比

场景Dictionary + lockConcurrentDictionary
高并发读所有读需排队(慢)几乎无锁(快)
低并发写串行写(中等)分段锁(较快)
高并发写严重瓶颈仍优于全局锁
代码简洁性需手动管理锁无需锁,API 更丰富

📊 在典型 Web 应用缓存场景(大量读 + 少量写),ConcurrentDictionary 性能可提升 5~10 倍。

🚫 五、常见误区

❌ 误区 1:认为dict[key] = value是原子的

// 错误!这实际上是:
//   if (exists) update; else add;
// 但中间可能被其他线程干扰
cache["key"] = newValue; // 不是原子操作!

✅ 正确做法:

cache.AddOrUpdate("key", newValue, (k, old) => newValue);

❌ 误区 2:在GetOrAdd中做非幂等操作

// 危险!工厂方法可能被多次调用(虽然最终只存一个结果)
var obj = cache.GetOrAdd("key", k => new ExpensiveObject()); // OK

// 更危险:有副作用的操作
cache.GetOrAdd("key", k => 
{
    Log("Creating instance"); // 可能被记录多次!
    return new MyService();
});

💡 虽然最终值是唯一的,但工厂方法可能被多个线程同时调用(.NET 6+ 已优化为单次调用,但旧版本不一定)。建议工厂方法无副作用、幂等

🛠 六、典型应用场景

1.内存缓存(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.计数器 / 统计

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

📏 七、性能调优建议

参数说明
concurrencyLevel预期并发更新线程数(默认为 CPU 核心数)
capacity初始容量(避免频繁扩容)
// 预期 8 个线程并发写,初始存 1000 项
var dict = new ConcurrentDictionary<string, Data>(
    concurrencyLevel: 8,
    capacity: 1000
);

💡 大多数场景用默认构造函数即可,除非你有明确的性能测试数据。

✅ 总结:何时使用ConcurrentDictionary?

场景推荐
多线程读写共享字典✅ 强烈推荐
高频读 + 低频写(如缓存)✅ 最佳选择
需要原子“获取或创建”语义✅ 必选
单线程或只读场景❌ 用普通 Dictionary 更轻量
需要保持插入顺序❌ 考虑 ImmutableDictionary 或加锁的 SortedDictionary

🔑 记住:ConcurrentDictionary 不是万能的,但它是在并发字典场景下最高效、最安全的选择。

到此这篇关于C# ConcurrentDictionary的使用小结的文章就介绍到这了,更多相关C# ConcurrentDictionary内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文