C# 多线程访问之 SemaphoreSlim(信号量)【C# 进阶】
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# 进阶】的更多相关文章
- 【C#】【Thread】Semaphore/SemaphoreSlim信号量
System.Threading.Semaphore 类表示一个命名(系统范围)信号量或本地信号量. 它是一个对 Win32 信号量对象的精简包装. Win32 信号量是计数信号量,可用于控制对资源池 ...
- WinformWPF 多线程访问控件【转】
大家知道WPF中多线程访问UI控件时会提示UI线程的数据不能直接被其他线程访问或者修改,该怎样来做呢? 分下面两种情况 1.WinForm程序 )第一种方法,使用委托: private delegat ...
- CoreData和SQLite多线程访问时的线程安全
关于CoreData和SQLite多线程访问时的线程安全问题 数据库读取操作一般都是多线程访问的.在对数据进行读取时,我们要保证其当前状态不能被修改,即读取时加锁,否则就会出现数据错误混乱.IOS中常 ...
- 5天玩转C#并行和多线程编程 —— 第四天 Task进阶
5天玩转C#并行和多线程编程系列文章目录 5天玩转C#并行和多线程编程 —— 第一天 认识Parallel 5天玩转C#并行和多线程编程 —— 第二天 并行集合和PLinq 5天玩转C#并行和多线程编 ...
- IOS 使用FMDB多线程访问数据库 及databaseislocked的问题
原理:文件数据库sqlite,同一时刻允许多个进程/线程读,但同一时刻只允许一个线程写.在操行写操作时,数据库文件被琐定,此时任何其他读/写操作都被阻塞,如果阻塞超过5秒钟(默认是5秒,能过重新编译s ...
- (原创)android Sqlite多线程访问异常解决方案
在开发Android的程序的时候sqlite数据库是经常用到的:在多线程访问数据库的时候会出现这样的异常:java.lang.IllegalStateException: Cannot perform ...
- [转] c#中 多线程访问winform控件
原文 c#中多线程访问winform控件的若干问题小结 我们在做winform应用的时候,大部分情况下都会碰到使用多线程控制界面上控件信息的问题.然而我们并不能用传统方法来解决这个问题,下面我将详细的 ...
- 关于CoreData和SQLite多线程访问时的线程安全问题
数据库读取操作一般都是多线程访问的.在对数据进行读取时,我们要保证其当前状态不能被修改,即读取时加锁,否则就会出现数据错误混乱.IOS中常用的两种数据持久化存储方式:CoreData和SQLite,两 ...
- 多线程访问winform控件出现异常的解决方法
一. 多线程访问winform控件出现异常的解决方法 1. 问题描述<1> 如果创建某控件的线程之外的其他线程试图调用该控件,则会引发一个 InvalidOperationExcept ...
随机推荐
- PerfView专题 (第十二篇):对 C# 下的 SDK 类库进行监控(大结局)
一:背景 本篇是我们系列文章的最后一篇,前面的文章中大多是在 CLR Runtime 以及 OS 层面进行监控来发现各种可疑的程序问题,除了这两个层面,其实我们还可以对 SDK 中一些类进行洞察,比如 ...
- KingbaseES 数据库删除功能组件
关键字: KingbaseES.卸载.删除功能 一.安装后检查 在安装完成后,可以通过以下几种方式进行安装正确性验证: 1. 查看安装日志,确认没有错误记录; 2. 查看开始菜单: 查看应用程 ...
- 通过VS下载的NuGet包,如何修改其下载存放路径?
一.了解NuGet包的默认存放路径 我们通过NuGet包管理器下载的引用包,默认是存放在C盘的,存储路径一般是: C:\Users\{系统用户名}\.nuget\packages 我们都知道,C盘的存 ...
- 华南理工大学 Python第1章课后小测
1.(单选)计算机有两个基本特性:功能性和()性.(本题分数:5)A) 可存储B) 可计算C) 可通信D) 可编程您的答案:D 正确率:100%2.(单选)计算机硬件可以直接识别和执行的程序设计语言 ...
- 关于在PyCharm中使用虚拟环境
Python虚拟环境的概念对于管理项目用到的第三方包真是好处多多,所以也想在PyCharm使用虚拟环境. 在这个过程中,遇到很多问题: 第一是使用Python创建虚拟环境,然后在PyCharm创建项目 ...
- Form表单数据
官方文档地址:https://fastapi.tiangolo.com/zh/tutorial/request-forms/ 接收的不是 JSON,而是表单字段时,要使用 Form 要使用表单,需预先 ...
- linux修改mysql的默认端口
本文是基于centos7的环境进行编写,如果是其他linux系统命令可能不一样,根据 1.首先我们查看mysql的默认端口 我们登录mysql到mysql查看mysql使用的端口 show globa ...
- PAT (Basic Level) Practice 1006 换个格式输出整数 分数 15
让我们用字母 B 来表示"百".字母 S 表示"十",用 12...n 来表示不为零的个位数字 n(<10),换个格式来输出任一个不超过 3 位的正整数. ...
- Java导出带格式的Excel数据到Word表格
前言 在Word中创建报告时,我们经常会遇到这样的情况:我们需要将数据从Excel中复制和粘贴到Word中,这样读者就可以直接在Word中浏览数据,而不用打开Excel文档.在本文中,您将学习如何使用 ...
- OFD是什么
OFD是什么? 原文链接:OFD是什么 OFD更多了解:OFD (Open Fixed layout Document/开放版式文档) OFD格式文件介绍 OFD(Open Fixed layout ...