面试题 关于线程池 和 ConcurrencyDictionary
今天面试,被问到几个问题,记录如下
1. 你说Task是从线程池中拿线程,那么线程池ThreadPool是谁创建的呢
ThreadPool 是CLR(.NET 运行时)在进程启动时创建并托管的,不是操作系统内核线程池,也不是我们代码手动 new 出来的。 CLR 会维护一组工作线程(Worker Thread) + IO 完成端口线程(IOCP Thread)。Task.Run 默认就是往 CLR ThreadPool 提交任务。线程池线程全部是后台线程。 但是不能无限制创建线程,因为每个线程都有独立栈(默认 1MB),大量原生线程会占用大量内存;CPU 频繁上下文切换,性能暴跌。线程池复用线程,减少上下文切换和内存开销。
2. ConcurrencyDictionary是线程安全的吗?
单个方法调用是线程安全。但是复合操作不是原子操作,这是最核心考点。
// ❌ 不安全,两步操作,中间可被其他线程修改 if (dict.ContainsKey(key)) { dict[key] = value; }
✅ 要用内置原子方法:TryAdd、TryGetValue、AddOrUpdate、GetOrAdd。
底层实现:分段锁(bucket 分段),不是全局锁。相比 Dictionary + lock,并发高的时候锁粒度更小,吞吐更好。
旧版本是细粒度锁;.NET Core/.NET5+ 做了优化,部分路径用 CAS 无锁。
2. GetOrAdd 坑点(高频追问)
dict.GetOrAdd(key, k => CreateExpensiveObject(k));
坑:工厂委托 CreateExpensiveObject 可能被多次执行 **! 就算最终只有一个对象写入字典,高并发下多个线程同时进入,工厂函数会并发跑多次(创建多个实例),只是最终只保留一个。 👉 如果构造对象很耗资源(数据库连接、大对象),工厂里面不能写昂贵操作。 解决:工厂只创建轻量对象,或者外部加锁保证昂贵逻辑只执行一次。
- ConcurrentDictionary 会有锁竞争吗? 答:会,分段锁,同一 bucket 下的 key 依然会锁竞争;不同 bucket 可以并行。
- 和 Dictionary + lock 对比? 答:低并发:Dictionary+lock 更快;高并发读写:ConcurrentDictionary 吞吐更好。
浙公网安备 33010602011771号