温故知新,CSharp遇见并发堆栈(ConcurrentStack)、并发队列(ConcurrentQueue)、并发数组(ConcurrentBag)、并发字典(ConcurrentDictionary)

前言

多线程问题的核心是控制对临界资源的访问, 在.NET Framework 4.0以后的版本中提供了命名空间:System.Collections.Concurrent来解决线程安全和lock锁性能问题,通过这个命名空间,能访问以下为并发做好了准备的集合。

  1. BlockingCollection与经典的阻塞队列数据结构类似,能够适用于多个任务添加和删除数据,提供阻塞和限界能力。
  2. ConcurrentBag提供对象的线程安全的无序集合
  3. ConcurrentDictionary 提供可有多个线程同时访问的键值对的线程安全集合
  4. ConcurrentQueue提供线程安全的先进先出集合
  5. 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)

image

定义它

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!

参考

posted @ 2022-11-25 09:42  TaylorShi  阅读(913)  评论(0编辑  收藏  举报