温故知新,CSharp遇见并发堆栈(ConcurrentStack)、并发队列(ConcurrentQueue)、并发数组(ConcurrentBag)、并发字典(ConcurrentDictionary)
前言
多线程问题的核心是控制对临界资源的访问, 在.NET Framework 4.0
以后的版本中提供了命名空间:System.Collections.Concurrent
来解决线程安全和lock
锁性能问题,通过这个命名空间,能访问以下为并发做好了准备的集合。
BlockingCollection
与经典的阻塞队列数据结构类似,能够适用于多个任务添加和删除数据,提供阻塞和限界能力。ConcurrentBag
提供对象的线程安全的无序集合ConcurrentDictionary
提供可有多个线程同时访问的键值对的线程安全集合ConcurrentQueue
提供线程安全的先进先出集合ConcurrentStack
提供线程安全的后进先出集合
这些集合通过使用比较并交换和内存屏障等技术,避免使用典型的互斥重量级的锁,从而保证线程安全和性能。
Monitor
Monitor是System.Threading
的静态类,提供加锁控制并发的静态方法。
定义它
private static object _lock = new object();
基本使用
try
{
if (Monitor.TryEnter(_lock, millisecondsTimeout: 5000))
{
Thread.Sleep(1000);
Console.WriteLine("1");
}
}
catch (Exception)
{
throw;
}
finally
{
Monitor.Exit(_lock);
}
Lock
Lock是Locker对象。
Locker对象相当于一把门锁(或者钥匙),后面代码块相当于屋里的资源。
哪个线程先控制这把锁,就有权访问代码块,访问完成后再释放权限,下一个线程再进行访问。
如果代码块中的逻辑执行时间很长,那么其他线程也会一直等下去,直到上一个线程执行完毕,释放锁。
它其实是一个语法糖,简化了锁的使用。
lock (x)
{
// Your code...
}
相当于
object __lockObj = x;
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
// Your code...
}
finally
{
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}
其本质还是通过System.Threading.Monitor
来加锁,但是使用起来更简单,所以大家用Lock
更多一些。
定义它
private static object _lock = new object();
基本使用
lock (_lock)
{
Thread.Sleep(1000);
Console.WriteLine("1");
}
Semaphore
信号量(Semaphore
)是System.Threading
的一个类,它相当于一个线程池,访问之前先获取一个信号,访问完成释放信号,只要信号量内有可用信号便可以访问,否则等待。
定义它
private static Semaphore _semaphore = new Semaphore(initialCount: 0, maximumCount: int.MaxValue);
基本使用
// 退出信号量,每次调用都会增加一个可用信号
_semaphore.Release();
Thread.Sleep(1000);
Console.WriteLine("1");
// 等待一个可用信号
_semaphore.WaitOne();
ConcurrentStack
并发堆栈(ConcurrentStack<T>
)是线程安全的后进先出(LIFO
)的集合。
特点
- 线程安全
- 后进先出(Last in, First out)
定义它
private static readonly ConcurrentStack<string> _stack = new ConcurrentStack<string>();
基本使用
// 在顶部插入多个对象
_stack.Push("first");
_stack.Push("second");
_stack.Push("third");
// 如果顶部有对象就返回,但不删除对象
_stack.TryPeek(out var result0);
Console.WriteLine(result0);
// 如果顶部有对象就返回,同时删除对象
_stack.TryPop(out var result1);
Console.WriteLine(result1);
// 如果顶部有对象就返回,同时删除对象
_stack.TryPop(out var result2);
Console.WriteLine(result2);
// 如果顶部没有对象,就返回空
_stack.TryPop(out var result3);
Console.WriteLine(result3);
Console.WriteLine("Hello World!");
执行结果
third
third
second
first
Hello World!
看得出,它是倒过来取得,也就是后进先出。
ConcurrentQueue
并发队列(ConcurrentQueue<T>
)是线程安全的先进先出(FIFO
)的集合。
特点
- 线程安全
- 先进先出(First Input, First Output)
定义它
private static readonly ConcurrentQueue<string> _queue = new ConcurrentQueue<string>();
基本使用
// 在末尾添加多个对象
_queue.Enqueue("first");
_queue.Enqueue("second");
_queue.Enqueue("third");
// 尝试拿出开头的对象,但不移除对象
_queue.TryPeek(out var result3);
Console.WriteLine(result3);
// 尝试拿出开头的对象,同时删除对象
_queue.TryDequeue(out var result4);
Console.WriteLine(result4);
// 尝试拿出开头的对象,同时删除对象
_queue.TryDequeue(out var result5);
Console.WriteLine(result5);
// 尝试拿出开头的对象,没有可拿的就返回空
_queue.TryDequeue(out var result6);
Console.WriteLine(result6);
Console.WriteLine("Hello World!");
执行结果
first
first
second
third
Hello World!
看得出,它是按加入顺序来取的,也就是先进先出。
ConcurrentBag
并发数组(ConcurrentBag<T>
)是线程安全的无序集合。
特点
- 线程安全
- 无序集合
定义它
private static readonly ConcurrentBag<string> _bag = new ConcurrentBag<string>();
基本使用
// 添加多个对象
_bag.Add("first");
_bag.Add("second");
_bag.Add("third");
// 尝试拿出一个对象,但不移除对象
_bag.TryPeek(out var result3);
Console.WriteLine(result3);
// 尝试拿出一个对象,同时删除对象
_bag.TryTake(out var result4);
Console.WriteLine(result4);
// 尝试拿出一个对象,同时删除对象
_bag.TryTake(out var result5);
Console.WriteLine(result5);
// 尝试拿出一个对象,没有可拿的就返回空
_bag.TryTake(out var result6);
Console.WriteLine(result6);
Console.WriteLine("Hello World!");
执行结果
third
third
second
first
Hello World!
看得出,它是倒过来取的,也就是后进先出。
ConcurrentDictionary
并发字典(ConcurrentDictionary<TKey,TValue>
)是可由多个线程同时访问的键/值对的线程安全集合。
特点
- 线程安全
- 键/值对集合
定义它
private static readonly ConcurrentDictionary<int, string> _dictionary = new ConcurrentDictionary<int, string>();
基本使用
// 尝试添加多个键值对象
_dictionary.TryAdd(1, "first");
_dictionary.TryAdd(2, "second");
_dictionary.TryAdd(3, "third");
// 尝试拿出指定键对应的值
_dictionary.TryGetValue(1, out var result3);
Console.WriteLine(result3);
// 尝试删除指定键并返回其值
_dictionary.TryRemove(1, out var result4);
Console.WriteLine(result4);
// 尝试更新指定键,从`second`更新为`second-v2`
_dictionary.TryUpdate(2, "second-v2", "second");
_dictionary.TryGetValue(2, out var result5);
Console.WriteLine(result5);
Console.WriteLine("Hello World!");
执行结果
first
first
second-v2
Hello World!
参考
ConcurrentStack<T>
类ConcurrentStack<T>.TryPeek(T)
方法ConcurrentQueue<T>
类ConcurrentQueue<T>.TryDequeue(T)
方法ConcurrentBag<T>
类ConcurrentBag<T>.TryTake(T)
方法ConcurrentDictionary<TKey,TValue>
类ConcurrentDictionary<TKey,TValue>.TryGetValue(TKey, TValue)
方法- C#并发安全集合ConcurrentBag取代List
- C# 使用Parallel并行对比lock锁和ConcurrentQueue并发性能
- 聊聊.net 并发控制,lock,Monitor,Semaphore,BlockingQueue,乐观锁串讲
- lock语句
- 各位大神,请教程序Exception_WasThrown的问题
- IEnumerable中的Any方法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2021-11-25 乘风破浪,遇见未来元宇宙(Metaverse)之NFT数字价值系统 - 行业名词词典60+