.NET 中的线程安全数据结构

在多线程编程中,线程安全的数据结构是确保数据一致性和避免竞争条件的关键。.NET 提供了多种线程安全的数据结构,适用于不同的场景,本篇将介绍它们的简单使用以及在 .NET Core 和 .NET Framework 中的可用性。

1. ConcurrentQueue

ConcurrentQueue 是一个线程安全的先进先出 (FIFO) 队列。它允许多个线程同时进行入队和出队操作,而不会导致数据不一致。

适用场景

  • 生产者-消费者模式:多个生产者线程将数据项添加到队列中,多个消费者线程从队列中取出数据项进行处理
  • 任务调度:将任务添加到队列中,由工作线程从队列中取出任务并执行

优点

  • 高效的并发操作:支持多个线程同时进行入队和出队操作
  • 无锁设计:内部使用无锁算法,避免了锁竞争,提高了性能
  • 易于使用:提供简单的 API,如EnqueueTryDequeue

可用性

  • .NET Framework 4.0 及以上
  • .NET Core 1.0 及以上

示例代码

using System.Collections.Concurrent;  var queue = new ConcurrentQueue<int>(); var cts = new CancellationTokenSource(); var token = cts.Token;  // 生产者任务 var producer = Task.Run(() => {     for (int i = 0; i < 10; i++)     {         queue.Enqueue(i);         Console.WriteLine($"Enqueued {i}");         Thread.Sleep(100); // 模拟生产延迟     } }, token);  // 消费者任务 var consumer = Task.Run(() => {     while (!token.IsCancellationRequested)     {         if (queue.TryDequeue(out int result))         {             Console.WriteLine($"Dequeued {result}");         }         Thread.Sleep(50); // 模拟消费延迟     } }, token);  await Task.WhenAll(producer); cts.Cancel(); // 停止消费者任务 await consumer; 

2. ConcurrentStack

ConcurrentStack 是一个线程安全的后进先出 (LIFO) 堆栈。它允许多个线程同时进行入栈和出栈操作。

适用场景

  • 深度优先搜索算法:在图或树结构中进行深度优先搜索时使用
  • 撤销操作:实现撤销功能时,将操作记录入栈,撤销时从栈中弹出操作

优点

  • 高效的并发操作:支持多个线程同时进行入栈和出栈操作
  • 无锁设计:内部使用无锁算法,避免了锁竞争,提高了性能
  • 易于使用:提供简单的 API,如PushTryPop

可用性

  • .NET Framework 4.0 及以上
  • .NET Core 1.0 及以上

示例代码

using System.Collections.Concurrent;  var stack = new ConcurrentStack<int>(); var cts = new CancellationTokenSource(); var token = cts.Token;  // 生产者任务 var producer = Task.Run(() => {     for (int i = 0; i < 10; i++)     {         stack.Push(i);         Console.WriteLine($"Pushed {i}");         Thread.Sleep(100); // 模拟生产延迟     } }, token);  // 消费者任务 var consumer = Task.Run(() => {     while (!token.IsCancellationRequested)     {         if (stack.TryPop(out int result))         {             Console.WriteLine($"Popped {result}");         }         Thread.Sleep(50); // 模拟消费延迟     } }, token);  await Task.WhenAll(producer); cts.Cancel(); // 停止消费者任务 await consumer; 

3. ConcurrentBag

ConcurrentBag 是一个线程安全的无序集合,适用于频繁添加和删除元素的场景。

适用场景

  • 任务池:将任务添加到集合中,工作线程从集合中取出任务并执行
  • 缓存:将临时数据存储在集合中,多个线程可以并发地添加和删除数据

优点

  • 高效的并发操作:支持多个线程同时进行添加和删除操作
  • 无锁设计:内部使用无锁算法,避免了锁竞争,提高了性能
  • 适用于无序数据:不关心元素顺序的场景非常适用

可用性

  • .NET Framework 4.0 及以上
  • .NET Core 1.0 及以上

示例代码

using System.Collections.Concurrent;  var bag = new ConcurrentBag<int>(); var cts = new CancellationTokenSource(); var token = cts.Token;  // 生产者任务 var producer = Task.Run(() => {     for (int i = 0; i < 10; i++)     {         bag.Add(i);         Console.WriteLine($"Added {i}");         Thread.Sleep(100); // 模拟生产延迟     } }, token);  // 消费者任务 var consumer = Task.Run(() => {     while (!token.IsCancellationRequested)     {         if (bag.TryTake(out int result))         {             Console.WriteLine($"Took {result}");         }         Thread.Sleep(50); // 模拟消费延迟     } }, token);  await Task.WhenAll(producer); cts.Cancel(); // 停止消费者任务 await consumer; 

4. ConcurrentDictionary<TKey, TValue>

ConcurrentDictionary<TKey, TValue> 是一个线程安全的键值对集合,类似于 Dictionary<TKey, TValue>。

适用场景

  • 缓存:存储键值对数据,多个线程可以并发地读取和写入缓存
  • 计数器:存储计数器数据,多个线程可以并发地更新计数器值

优点

  • 高效的并发操作:支持多个线程同时进行读取和写入操作
  • 原子操作:支持原子操作,如AddOrUpdateGetOrAdd,确保数据一致性
  • 灵活性:提供丰富的 API,支持多种操作

可用性

  • .NET Framework 4.0 及以上
  • .NET Core 1.0 及以上

示例代码

using System.Collections.Concurrent;  var dictionary = new ConcurrentDictionary<int, string>();  // 添加元素 var addTask = Task.Run(() => {     for (int i = 0; i < 10; i++)     {         dictionary.TryAdd(i, $"value{i}");         Console.WriteLine($"Added key {i} with value value{i}");     } });  // 更新元素 var updateTask = Task.Run(() => {      for (int i = 0; i < 10; i++)     {         var ii = i;         dictionary.AddOrUpdate(i, $"new_value{i}", (key, oldValue) => $"new_value{ii}");         Console.WriteLine($"Updated key {i} with value new_value{i}");     }  });  // 读取元素 var readTask = Task.Run(() => {     foreach (var key in dictionary.Keys)     {         if (dictionary.TryGetValue(key, out string? value))         {             Console.WriteLine($"Key {key} has value {value}");         }     } });  await Task.WhenAll(addTask, updateTask, readTask); 

5. BlockingCollection

BlockingCollection 提供线程安全的添加和移除操作,并支持阻塞和限界功能。可以与ConcurrentQueue<T>, ConcurrentStack<T>, ConcurrentBag<T>等一起使用。

适用场景

  • 生产者-消费者模式:多个生产者线程将数据项添加到集合中,多个消费者线程从集合中取出数据项进行处理
  • 任务调度:将任务添加到集合中,由工作线程从集合中取出任务并执行

优点

  • 阻塞操作:支持阻塞添加和移除操作,适用于生产者-消费者模式
  • 限界功能:支持设置集合的最大容量,防止过度填充
  • 灵活性:可以与多种集合类型一起使用,如ConcurrentQueue<T>

可用性

  • .NET Framework 4.0 及以上
  • .NET Core 1.0 及以上

示例代码

using System.Collections.Concurrent;  var collection = new BlockingCollection<int>(boundedCapacity: 5); var cts = new CancellationTokenSource(); var token = cts.Token;  // 生产者任务 var producer = Task.Run(() => {     for (int i = 0; i < 10; i++)     {         collection.Add(i);         Console.WriteLine($"Added {i}");         Thread.Sleep(100); // 模拟生产延迟     }     collection.CompleteAdding(); }, token);  // 消费者任务 var consumer = Task.Run(() => {     foreach (var item in collection.GetConsumingEnumerable(token))     {         Console.WriteLine($"Consumed {item}");         Thread.Sleep(50); // 模拟消费延迟     } }, token);  await Task.WhenAll(producer, consumer); 

6. ImmutableList

ImmutableList 是线程安全的,因为所有修改操作都会返回一个新的集合实例。

适用场景

  • 配置数据:存储配置数据,多个线程可以并发地读取配置数据,而无需担心数据被修改
  • 快照:在某个时间点获取数据的快照,多个线程可以并发地读取快照数据

优点

  • 天然线程安全:由于集合不可变,多个线程可以安全地并发读取
  • 数据一致性:所有修改操作都会返回一个新的集合实例,保证数据一致性
  • 易于使用:提供丰富的 API,支持多种操作

可用性

  • .NET Framework 4.5 及以上(需要安装 System.Collections.Immutable NuGet 包)
  • .NET Core 1.0 及以上(需要安装 System.Collections.Immutable NuGet 包)

示例代码

var list = ImmutableList.Create(1, 2, 3); var newList = list.Add(4);  Console.WriteLine(string.Join(", ", newList)); // 输出 1, 2, 3, 4 

7. SynchronizedCollection

SynchronizedCollection 是一个线程安全的集合,适用于需要同步访问的场景。

适用场景

  • 共享资源管理:管理共享资源的集合,多个线程可以并发地访问和修改集合
  • 事件订阅:存储事件订阅者,多个线程可以并发地添加和移除订阅者

优点

  • 内置同步机制:自动处理同步,确保线程安全
  • 易于使用:提供简单的 API,如AddRemove
  • 灵活性:支持多种集合操作

可用性

  • .NET Framework 3.5 及以上
  • .NET Core 1.0 及以上

示例代码

var collection = new SynchronizedCollection<int>(); collection.Add(1); collection.Add(2);  foreach (var item in collection) {     Console.WriteLine(item); // 输出 1 和 2 } 

8. SynchronizedReadOnlyCollection

SynchronizedReadOnlyCollection 是一个线程安全的只
读集合。

适用场景

  • 配置数据:存储只读的配置数据,多个线程可以并发地读取配置数据
  • 共享数据:存储共享数据,多个线程可以并发地读取数据,而无需担心数据被修改

优点

  • 内置同步机制:自动处理同步,确保线程安全
  • 易于使用:提供简单的 API,如ContainsCopyTo
  • 数据保护:只读特性确保数据不会被修改

可用性

  • .NET Framework 3.0 及以上
  • .NET Core 1.0 及以上

示例代码

var list = new List<int> { 1, 2, 3 }; var readOnlyCollection = new SynchronizedReadOnlyCollection<int>(list);  foreach (var item in readOnlyCollection) {     Console.WriteLine(item); // 输出 1, 2, 3 } 

9. SynchronizedKeyedCollection<K, T>

SynchronizedKeyedCollection<K, T> 是一个线程安全的键控集合。

使用场景

  • 缓存:存储键控数据,多个线程可以并发地读取和写入缓存
  • 资源管理:管理键控资源的集合,多个线程可以并发地访问和修改集合

优点

  • 内置同步机制:自动处理同步,确保线程安全
  • 键控访问:支持通过键快速访问元素
  • 灵活性:支持多种集合操作

可用性

  • .NET Framework 3.0 及以上
  • .NET Core 1.0 及以上

示例代码

public class MyItem {     public int Id { get; set; }     public string Name { get; set; } }  var collection = new SynchronizedKeyedCollection<int, MyItem>(item => item.Id); collection.Add(new MyItem { Id = 1, Name = "Item1" }); collection.Add(new MyItem { Id = 2, Name = "Item2" });  foreach (var item in collection) {     Console.WriteLine(item.Name); // 输出 Item1 和 Item2 } 

发表评论

相关文章