C#中并发集合---线程安全但无需加锁的高性能结构

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. 核心定位

目标 实现手段
高并发、低争用 无锁算法 (主要基于 CASSpinWait
线程安全 内部已保证,调用方 无需任何外部锁
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 重要注意事项

  1. 完成标记 :调用 CompleteAdding() 后,不能再添加新元素
  2. 异常处理 :在 CompleteAdding() 后调用 Take() 会抛出 InvalidOperationException
  3. 资源释放 :使用 using 语句或手动调用 Dispose()
  4. 性能考虑
    • 合理设置边界容量避免内存问题
    • 使用 TryAdd/TryTake 配合超时避免永久阻塞
    • 考虑使用 CancellationToken 支持取消操作

3.4 适用场景

  • 数据管道:多阶段数据处理
  • 任务队列:工作线程池的任务分配
  • 实时数据处理:日志处理、消息队列
  • 资源池管理:连接池、对象池

小结:

BlockingCollection<T> 提供了强大的生产者-消费者模式实现,通过阻塞机制简化了线程间的协调工作,是构建高效并发应用程序的重要工具。

参考文章:

C#中并发集合---线程安全但无需加锁的高性能结构

ConcurrentQueue Class

相关推荐
FuckPatience4 小时前
.netcoreapp2.0与.Net Core是什么关系
c#·.net·.netcore
Dr.勿忘4 小时前
开源Unity小框架:高效单例与模块化设计
游戏·unity·开源·c#·游戏引擎·游戏程序·gamejam
小码编匠5 小时前
.NET 免费开源的 Word 处理神器
后端·c#·.net
烛阴6 小时前
C#从数组到集合的演进与最佳实践
前端·c#
初九之潜龙勿用6 小时前
C# 操作Word模拟解析HTML标记输出带格式的文本
开发语言·c#·word·office
唐青枫8 小时前
一文理解 C#.NET Tuples:从基础到高级应用
c#·.net
2501_9411474215 小时前
Java高性能消息队列与Kafka实战分享:分布式消息处理与性能优化经验
c#·linq
Charles_go17 小时前
C#中级48、Debug版本和Release版本有什么区别
java·linux·c#