C# 多线程访问之 SemaphoreSlim(信号量)【进阶篇】

c#,多线程,访问,semaphoreslim,信号量,进阶篇 · 浏览次数 : 1732

小编点评

**SemaphoreSlim 是一个轻量级的线程同步工具,它可以用来限制对共享资源的并发访问数量。** **主要特点:** * **可控数量:**允许指定最大可同时处理的线程数量。 * **延迟初始化:**允许在创建 SemaphoreSlim 实例之前延迟初始化。 * **基于内核的等待句柄:**提供基于内核的等待句柄,以提高性能。 * **支持取消:**支持使用取消标记取消等待。 **用法示例:** ```csharp // 创建 SemaphoreSlim 对象,指定初始数量为 4 SemaphoreSlim semaphore = new SemaphoreSlim(4, 4); // 进入信号量,并打印线程 ID Console.WriteLine($\"任务 {Task.CurrentId} 进入 semaphore\"); Console.WriteLine($\"ProcessorId {Thread.GetCurrentProcessorId()}\"); // 释放信号量 semaphore.Release(); // 等待所有线程完成 Task.WaitAll(tasks); // 打印 SemaphoreSlim 的现有信号量 Console.WriteLine($\"semaphore 现有信号量 {semaphore.CurrentCount}\"); ``` **属性:** * **AvailableWaitHandle:**返回一个 WaitHandle 对象,表示等待对共享资源的独占访问操作的独占锁。 * **CurrentCount:**用于记录对于 SemaphoreSlim 对象可以处理的线程数量。 * **ReleaseCount:**用于指定释放信号量数量的线程数。 **注意:** * SemaphoreSlim 不是线程安全的,不能与其他线程并行使用。 * 如果初始数量为 0,则需要手动释放指定数量的信号量。 * 在调用 `Wait()` 或 `WaitAsync()` 方法之前,需要确保线程已完成进入信号量。

正文

SemaphoreSlim 是对可同时访问某一共享资源或资源池的线程数加以限制的 Semaphore 的轻量替代,也可在等待时间预计很短的情况下用于在单个进程内等待。

由于 SemaphoreSlim 更加轻量、快速,因此推荐使用,本文也着重介绍。

一、简介

相较于线程锁的使一块代码只能一个线程访问,SemaphoreSlim 则是让同一块代码让多个线程同时访问,并且总数量可控。

SemaphoreSlim 尽可能多地依赖公共语言运行时 (CLR) 提供的同步基元。 还提供延迟初始化、基于内核的等待句柄。

SemaphoreSlim 也支持使用取消标记,但不支持命名信号量或使用用于同步的等待句柄。

线程通过调用从 WaitHandle 类中继承的 WaitOne 方法进入信号量,无论对于 System.Threading.Semaphore 对象、SemaphoreSlim.Wait 或 SemaphoreSlim.WaitAsync 方法还是 SemaphoreSlim 对象都适用。

当调用返回时,信号量计数会减少,当线程请求进入且计数为零时,此线程受到阻止。 线程通过调用 Semaphore.Release 或 SemaphoreSlim.Release 方法释放信号量时,允许受阻线程进入,此时信号量计数会增加。

受阻线程进入信号量无保证的顺序,比如先进先出 (FIFO) 或按后进先出 (LIFO)。

二、用法示例

关于 SemaphoreSlim、Wait()、Release() 的一个示例。

特别注意:若初始信号量为 0,则需要手动释放(Release())信号量。

class Program
{
    private static SemaphoreSlim semaphore;
    private static int padding; // 增加固定时间间隔,使输出更有序
    static void Main(string[] args)
    { 
        // 创建 semaphore 对象
        semaphore = new SemaphoreSlim(0, 4); // (初始数量,最大数量)
        Console.WriteLine($"semaphore 中现有 {semaphore.CurrentCount} 个信号量");
        Task[] tasks = new Task[10];
        for (int i = 0; i <= 9; i++)
        {
            tasks[i] = Task.Run(() =>
            {
                Console.WriteLine($"任务 {Task.CurrentId} 准备进入 semaphore");
                int semaphoreCount;
                semaphore.Wait(); // 调用 Wait() 方法,标记等待进入信号量
                try
                {
                    Interlocked.Add(ref padding, 100);
                    Console.WriteLine($"任务 {Task.CurrentId} 进入 semaphore");
                    Thread.Sleep(1000 + padding);
                }
                finally
                {
                    semaphoreCount = semaphore.Release(); // 调用 Release() 方法,释放信号量
                    // semaphoreCount:释放当前信号量之前的信号量
                }
                Console.WriteLine($"任务 {Task.CurrentId} 完成,释放 semaphore 一个信号量;释放前信号量:{semaphoreCount}");
            });
        }
        Thread.Sleep(500); // 暂时阻塞主线程,让全部线程到位
        Console.Write("主线程调用 Release(4) 释放四个信号量--> ");
        semaphore.Release(4); // 由于初始数量为 0 所以需要手动释放信号量
        Console.WriteLine($"semaphore 现有信号量 {semaphore.CurrentCount}");
        Task.WaitAll(tasks); // 等待全部线程完成
        Console.WriteLine("主线程退出");
    }
}

 输出结果:

  

 三、属性 or 函数 or 方法释义

属性-AvailableWaitHandle

  返回一个 WaitHandle 对象,即封装等待对共享资源的独占访问的操作系统对象。

属性-CurrentCount

  指的是对于 SemaphoreSlim 对象,可以输入信号量的剩余线程数。

  属性的初始值 CurrentCount 由对类构造函数的 SemaphoreSlim 调用设置。 每次对 Wait 或 WaitAsync 方法的调用会递减,并按对 Release 方法的每此调用递增。

构造方法-SemaphoreSlim(Int32)

public SemaphoreSlim (int initialCount);

  初始化 SemaphoreSlim 类的新实例,以指定可同时授予的请求的初始数量,但未指定最大请求数。

  若初始数量为 0,则需要手动释放指定数量的信号量;若小于 0,则抛出异常:ArgumentOutOfRangeException。

构造方法-SemaphoreSlim(Int32, Int32)

public SemaphoreSlim (int initialCount, int maxCount);

  初始化 SemaphoreSlim 类的新实例,同时指定可同时授予的请求的初始数量和最大数量

  若initialCount 小于 0,或 initialCount 大于 maxCount,或 maxCount 小于等于 0,则抛出异常:ArgumentOutOfRangeException。

方法-Dispose

   用于释放由 SemaphoreSlim 类的当前实例使用的所有资源。

  SemaphoreSlim 与大多数成员不同,Dispose 不是线程安全的,不能与此实例的其他成员同时使用。

// 若要释放托管资源和非托管资源,则为 true(缺省默认);
// 若仅释放非托管资源,则为 false
protected virtual void Dispose (bool disposing);

 方法-Release

  对 Release() 方法的调用将属性递增一个 CurrentCount 数。 如果在调用此方法之前 CurrentCount 属性值为零,该方法还允许调用 Wait 或 WaitAsync 方法,阻止一个线程或任务进入信号灯。

// 释放 SemaphoreSlim 对象指定的次数
public int Release (int releaseCount);

方法-Wait

  阻止当前线程,直至它可进入 SemaphoreSlim 对象为止。

// 使用 TimeSpan 来指定超时,同时监视取消动作 CancellationToken
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
public bool Wait (TimeSpan timeout, System.Threading.CancellationToken cancellationToken);
// TimeSpan 表示要等待的毫秒数,-1 代表无限等待,0 代表立即返回-测试用例
// 阻止当前线程,直至它可进入 SemaphoreSlim 为止
// 并使用 32 位带符号整数来指定超时(-1 代表无限等待),同时监视取消操作 CancellationToken
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
public bool Wait (int millisecondsTimeout, System.Threading.CancellationToken cancellationToken);
// 返回值 bool:如果当前线程成功进入 SemaphoreSlim,则为 true;否则为 false
// 阻止当前线程,直至它可进入 SemaphoreSlim 为止
[System.Runtime.Versioning.UnsupportedOSPlatform("browser")]
public void Wait ();

方法-WaitAsync

  此为 Wait 方法的异步方式,优势在于对线程不会独占,即不会独占当前线程直到释放信号量。

  若将本文第二部分中的代码:(两处修改)

semaphore.Wait();
// 1/2 改为以下,异步方式
semaphore.WaitAsync();

// 2/2 并记录线程 ID(在如下位置添加一行打印信息)
try
{
    Console.WriteLine($"任务 {Task.CurrentId} 进入 semaphore");
    Console.WriteLine($"ProcessorId {Thread.GetCurrentProcessorId()}");// 新增行
    Interlocked.Add(ref padding, 100);
    Thread.Sleep(1000 + padding);
}

  输出的结果:(可见打印出来的线程 ID 有相同的情况,说明并非独占)

   

参考官方:SemaphoreSlim 类

注:个人记录,有问题欢迎指正。

与C# 多线程访问之 SemaphoreSlim(信号量)【进阶篇】相似的内容:

C# 多线程访问之 SemaphoreSlim(信号量)【进阶篇】

SemaphoreSlim 可对同时访问某一共享资源或资源池的线程数加以限制,相较于 Semaphore 更加轻量、快速,因此推荐使用,本文也着重介绍。

C#中使用CAS实现无锁算法

CAS 的基本概念 CAS(Compare-and-Swap)是一种多线程并发编程中常用的原子操作,用于实现多线程间的同步和互斥访问。 它操作通常包含三个参数:一个内存地址(通常是一个共享变量的地址)、期望的旧值和新值。 CompareAndSwap(内存地址,期望的旧值,新值) CAS 操作会比较

一文搞懂C++继承、多继承、菱形继承、虚继承

继承 目录继承继承继承的访问权限子类赋值给父类赋值兼容规则“天然”的行为验证:1. 其他权限继承能否支持赋值兼容规则2.是否"天然",有没有产生临时变量继承中的作用域继承的构造函数继承的拷贝构造继承的operator=继承的析构函数析构顺序析构的特殊处理继承中的static成员设计一个不能被继承的类

C++的动态分派在HotSpot VM中的重要应用

众所周知,多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。C++ 和 Java 作为当前最为流行的两种面向对象编程语言,其内部对于多态的支持对于单继承的实现非常类似。 首先来体现一下C++的动态分派,如下: class Base1{ pub

分布式事务保姆级教程

⼀、本地事务 1、ACID特性 原⼦性(A) ⼀致性(C) 隔离性(I) 持久性(D) 2、事务的隔离级别 两个或多个事务并发操作相同的数据的时候事务之间的相互访问关系 查询当前隔离级别:select @@tx_isolation 设置隔离级别:set session transaction iso

C#实现多线程的几种方式

前言 多线程是C#中一个重要的概念,多线程指的是在同一进程中同时运行多个线程的机制。多线程适用于需要提高系统并发性、吞吐量和响应速度的场景,可以充分利用多核处理器和系统资源,提高应用程序的性能和效率。 多线程常用场景 CPU 密集型任务. I/O 密集型任务. 并发请求处理. 大数据处理等. 什么是

C#的多线程UI窗体控件显示方案 - 开源研究系列文章

上次编写了《LUAgent服务器端工具》这个应用,然后里面需要新启动一个线程去对文件进行上传到FTP服务器,但是新线程里无法对应用主线程UI的内容进行更改,所以就需要在线程里设置主UI线程里控件信息的方法,于是就有了此博文。此文记录的是一种高级用法。 为了实际的使用,笔者将线程操作放在独立的类当中,

4.7 C++ Boost 多线程并发库

C++语言并没有对多线程与网络的良好支持,虽然新的C++标准加入了基本的`thread`库,但是对于并发编程的支持仍然很基础,Boost库提供了数个用于实现高并发与网络相关的开发库这让我们在开发跨平台并发网络应用时能够像Java等语言一样高效开发。thread库为C++增加了多线程处理能力,其主要提供了清晰的,互斥量,线程,条件变量等,可以很容易的实现多线程应用开发,而且该库是可跨平台的,并且支持

【C#异步】异步多线程的本质,上下文流转和同步

引言 net同僚对于async和await的话题真的是经久不衰,这段时间又看到了关于这方面的讨论,最终也没有得出什么结论,其实要弄懂这个东西,并没有那么复杂,简单的从本质上来讲,就是一句话,async 和await异步的本质就是状态机+线程环境上下文的流转,由状态机向前推进执行,上下文进行环境切换,

C#异步编程是怎么回事(番外)

在上一篇通信协议碰到了多线程,阻塞、非阻塞、锁、信号量...,会碰到很多问题。因此我感觉很有必要研究多线程与异步编程。 首先以一个例子开始 我说明一下这个例子。 这是一个演示异步编程的例子。 输入job [name],在一个同步的Main方法中,以一发即忘的方式调用异步方法StartJob()。 输