在 C# 中,并发集合提供了线程安全的数据结构,允许多个线程同时访问而无需显式的锁定。这些集合位于 System.Collections.Concurrent
命名空间中,并广泛用于多线程和并发编程中。以下是一些常用的并发集合及其使用场景和示例:
1. ConcurrentDictionary<TKey, TValue>
功能
- 线程安全的键值对集合,允许多个线程同时读取和写入数据。
- 内置了锁的分段机制,以便多个线程可以高效并发操作。
使用场景
- 当多个线程需要共享和更新键值对时使用。
- 常用于缓存、共享状态的并发访问。
示例
using System.Collections.Concurrent;
ConcurrentDictionary<int, string> dict = new ConcurrentDictionary<int, string>();
// 添加或更新键值对
dict.TryAdd(1, "value1");
dict[2] = "value2";
// 尝试获取值
if (dict.TryGetValue(1, out string value))
{
Console.WriteLine($"Key 1: {value}");
}
// 添加或更新现有的值
dict.AddOrUpdate(1, "newValue", (key, oldValue) => oldValue + " updated");
2. ConcurrentQueue<T>
功能
- 线程安全的队列,遵循先进先出(FIFO)原则。
- 无需锁定的并发访问,可以安全地从多个线程同时进行入队和出队操作。
使用场景
- 适用于生产者-消费者模式,其中多个线程添加任务,多个线程处理任务。
- 用于任务调度、事件处理等场景。
示例
using System.Collections.Concurrent;
ConcurrentQueue<int> queue = new ConcurrentQueue<int>();
// 入队操作
queue.Enqueue(1);
queue.Enqueue(2);
// 出队操作
if (queue.TryDequeue(out int result))
{
Console.WriteLine($"Dequeued: {result}");
}
3. ConcurrentStack<T>
功能
- 线程安全的栈,遵循后进先出(LIFO)原则。
- 支持并发的 Push 和 Pop 操作,无需显式锁定。
使用场景
- 适用于后进先出的任务处理模式,例如需要最近任务优先处理。
示例
using System.Collections.Concurrent;
ConcurrentStack<int> stack = new ConcurrentStack<int>();
// 入栈操作
stack.Push(1);
stack.Push(2);
// 出栈操作
if (stack.TryPop(out int result))
{
Console.WriteLine($"Popped: {result}");
}
4. ConcurrentBag<T>
功能
- 线程安全的无序集合,适合频繁的添加和移除操作。
- 每个线程都有自己的本地存储,减少了竞争。
使用场景
- 适合需要快速添加或删除项目但不关心顺序的情况,如需要保存大量对象用于重用。
示例
using System.Collections.Concurrent;
ConcurrentBag<int> bag = new ConcurrentBag<int>();
// 添加元素
bag.Add(1);
bag.Add(2);
// 移除元素
if (bag.TryTake(out int result))
{
Console.WriteLine($"Took: {result}");
}
5. BlockingCollection<T>
功能
- 提供了线程安全的集合,支持阻塞和限速的操作。
- 常与
ConcurrentQueue<T>
或ConcurrentStack<T>
搭配使用。 - 支持生产者-消费者模式,可以在集合为空时自动阻塞消费者线程,或者在达到容量时阻塞生产者线程。
使用场景
- 适用于生产者-消费者场景,其中生产者生产数据,消费者等待并消费数据。
- 支持最大容量限制,控制数据的生产和消费速率。
示例
using System.Collections.Concurrent;
BlockingCollection<int> collection = new BlockingCollection<int>(boundedCapacity: 5);
// 生产者任务
Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
collection.Add(i);
Console.WriteLine($"Produced: {i}");
}
collection.CompleteAdding();
});
// 消费者任务
Task.Run(() =>
{
foreach (var item in collection.GetConsumingEnumerable())
{
Console.WriteLine($"Consumed: {item}");
}
});
6. ConcurrentQueue<T>
vs BlockingCollection<T>
ConcurrentQueue<T>
:非阻塞,适合无等待的场景,多个线程可以并发读取和写入。BlockingCollection<T>
:阻塞,当队列为空时,消费者等待;当队列达到上限时,生产者等待,适用于有生产消费节奏控制的场景。
7. ConcurrentDictionary<TKey, TValue>
vs Dictionary<TKey, TValue>
ConcurrentDictionary<TKey, TValue>
:线程安全,允许并发的读写操作,内部采用了分段锁以提高并发性能。Dictionary<TKey, TValue>
:非线程安全,需要通过显式的锁来保证安全性。
总结
并发集合提供了一种简化并发编程的方式,避免了手动使用锁来保护共享数据。在多线程程序中,选择合适的并发集合可以显著提高性能并减少代码的复杂性。常用并发集合包括:
ConcurrentDictionary<TKey, TValue>
:适合多线程安全的键值对存储。ConcurrentQueue<T>
和ConcurrentStack<T>
:适合先进先出和后进先出的任务调度。ConcurrentBag<T>
:适合无序的快速添加和删除。BlockingCollection<T>
:适合生产者-消费者模式。
了解这些并发集合的特点和使用场景,将帮助你在多线程编程中编写更高效和安全的代码。