C#中并发集合---线程安全但无需加锁的高性能结构
C#中并发集合他们分别是ConCurrentQueue、ConcurrentDictionary、BlockingConllection。
| 集合 | 功能 | 最佳使用场景 |
|---|---|---|
| ConcurrentQueue | 线程安全队列(FIFO) | 生产者 - 消费者、消息队列 |
| ConcurrentDictionary | 线程安全字典 | 缓存、计数器、共享状态管理 |
| BlockingCollection | 内置阻塞和限界队列 | 专业的生产者 - 消费者模型 |
并发集合内部用细粒度锁 + 原子操作 ,比你自己写 lock 可靠十倍、性能更高、有成熟测试。
下面我们来说别介绍他们。
1.ConcurrentQueue< T > Class
ConcurrentQueue<T> 是 .NET 专门为"多线程同时读写"场景设计的 线程安全、无锁(lock-free) 的先进先出队列。ConcurrentQueue<T> 是 .NET 中一个高性能的线程安全先进先出(FIFO)集合 ,它通过巧妙的分段存储 和无锁(lock-free)技术 (如原子操作和自旋等待),让你能在多线程环境中安全地进行入队和出队操作,而无需使用繁重的锁机制。
它位于 System.Collections.Concurrent 命名空间,从 .NET 4 开始提供,是"并发集合"家族中最常用的结构之一。
1.1. 核心定位
| 目标 | 实现手段 |
|---|---|
| 高并发、低争用 | 无锁算法 (主要基于 CAS 与 SpinWait) |
| 线程安全 | 内部已保证,调用方 无需任何外部锁 |
| FIFO 语义 | 严格先进先出,与 Queue<T> 保持一致 |
1.2.代码测试
C#
// 创建一个对列
// 1. 创建队列
ConcurrentQueue<int> numbers = new ConcurrentQueue<int>();
// 2. 并行入队:多个任务同时添加数据
Task producer1 = Task.Run(() => {
for (int i = 0; i < 5; i++)
{
numbers.Enqueue(i);
Console.WriteLine($"Producer1 enqueued: {i}");
}
});
Task producer2 = Task.Run(() => {
for (int i = 10; i < 15; i++)
{
numbers.Enqueue(i);
Console.WriteLine($"Producer2 enqueued: {i}");
}
});
// 等待生产者完成
Task.WaitAll(producer1, producer2);
// 3. 并行出队:多个任务同时处理数据
Task consumer1 = Task.Run(() => {
while (numbers.TryDequeue(out int item))
{
Console.WriteLine($"Consumer1 processed: {item}");
}
});
Task consumer2 = Task.Run(() => {
while (numbers.TryDequeue(out int item))
{
Console.WriteLine($"Consumer2 processed: {item}");
}
});
// 等待消费者完成
Task.WaitAll(consumer1, consumer2);
// 4. 检查队列最终状态
Console.WriteLine($"Final count: {numbers.Count}");
Console.WriteLine($"Is empty: {numbers.IsEmpty}");
Console.ReadKey();
代码输出结果:

小结:
ConcurrentQueue<T> 把 lock-free 算法 + 分段链表 封装成极简 API:
在多线程场景下,调用者无需加任何锁 ,就能获得 高吞吐量、严格 FIFO 的线程安全队列,是 .NET 并发编程的"默认首选"队列结构。
2.ConcurrentDictionary<TKey,TValue> Class
ConcurrentDictionary<TKey, TValue> 是 .NET 中一个线程安全的哈希表实现,它通过细粒度锁和锁自由技术,让你能在多线程环境中安全地进行键值对操作,而无需使用外部同步机制。
2.1.核心特性
线程安全:多个线程可以同时读写而不会导致数据损坏
高性能:使用细粒度锁(每个哈希桶独立加锁)减少锁竞争
原子操作 :提供多种原子操作方法,如 AddOrUpdate, GetOrAdd
无锁读取:读操作在大多数情况下不需要加锁
2.2 代码测试
c#
ConcurrentDictionary<int, string> data = new ConcurrentDictionary<int, string>();
// 多个线程同时写入
Parallel.For(0, 10, i => {
data.TryAdd(i, $"Value_{i}");
Console.WriteLine($"Thread added: {i}");
});
// 多个线程同时读取
Parallel.For(0, 10, i => {
if (data.TryGetValue(i, out string value))
{
Console.WriteLine($"Thread read: {value}");
}
});
Console.WriteLine($"Total items: {data.Count}");
Console.ReadKey();
代码运行结果:

2.3 适用场景
- 共享缓存:多线程共享的缓存数据结构
- 统计计数:实时统计和计数器
- 资源池:连接池、对象池等资源管理
- 配置存储:运行时可能被多个线程修改的配置信息
小结:
ConcurrentDictionary<TKey, TValue> 通过其细粒度锁和优化算法,在保证线程安全的同时提供了接近普通字典的性能。它是构建高性能并发应用程序的重要工具,特别适合读多写少的场景。
3.BlockingCollection< T > Class
BlockingCollection<T> 是 .NET 中一个线程安全的阻塞集合,它为实现生产者-消费者模式提供了强大的支持,可以在集合为空时阻塞消费者线程,在集合满时阻塞生产者线程。
3.1 核心特性
- 边界限制:可以设置集合的最大容量
- 阻塞操作:当集合为空时,取操作会阻塞;当集合满时,添加操作会阻塞
- 多种后端存储 :默认使用
ConcurrentQueue<T>,也支持ConcurrentStack<T>等 - 取消支持 :支持通过
CancellationToken取消阻塞操作 - 完成标记 :可以通过
CompleteAdding()标记集合不再接受新元素
3.2 代码测试:
C#
// 创建阻塞集合(默认使用ConcurrentQueue,最大容量为10)
BlockingCollection<int> collection = new BlockingCollection<int>(10);
// 生产者任务
Task producer = Task.Run(() =>
{
for (int i = 1; i <= 5; i++)
{
collection.Add(i);
Console.WriteLine($"生产者添加: {i}");
Thread.Sleep(500); // 模拟生产耗时
}
collection.CompleteAdding(); // 标记生产完成
Console.WriteLine("生产者完成");
});
// 消费者任务
Task consumer = Task.Run(() =>
{
try
{
while (!collection.IsCompleted)
{
int item = collection.Take(); // 如果集合为空会阻塞
Console.WriteLine($"消费者处理: {item}");
Thread.Sleep(1000); // 模拟消费耗时
}
}
catch (InvalidOperationException)
{
Console.WriteLine("集合已关闭");
}
Console.WriteLine("消费者完成");
});
Task.WaitAll(producer, consumer);
代码运行结果:

3.3 重要注意事项
- 完成标记 :调用
CompleteAdding()后,不能再添加新元素 - 异常处理 :在
CompleteAdding()后调用Take()会抛出InvalidOperationException - 资源释放 :使用
using语句或手动调用Dispose() - 性能考虑 :
- 合理设置边界容量避免内存问题
- 使用
TryAdd/TryTake配合超时避免永久阻塞 - 考虑使用
CancellationToken支持取消操作
3.4 适用场景
- 数据管道:多阶段数据处理
- 任务队列:工作线程池的任务分配
- 实时数据处理:日志处理、消息队列
- 资源池管理:连接池、对象池
小结:
BlockingCollection<T> 提供了强大的生产者-消费者模式实现,通过阻塞机制简化了线程间的协调工作,是构建高效并发应用程序的重要工具。
参考文章: