目录

什么是 SemaphoreSlim

SemaphoreSlim 的核心方法

构造函数

等待方法

释放方法

基本使用模式

同步使用模式

异步使用模式(推荐在 API 中使用)

在 Web 开发中的常见用途

1. 限制 API 接口的并发请求数

2. 保护共享资源的并发访问

3. 控制外部服务的调用频率

4. 实现分布式锁的本地补充

注意事项与最佳实践

1. 确保正确释放信号量

2. 合理设置信号量的生命周期

3. 避免过度限制并发

4. 注意异步操作中的取消机制

5. 警惕死锁风险

6. 结合限流中间件使用

7. 多实例部署的局限性

总结


在.NET Core API 开发中,并发控制是保证系统稳定性和数据一致性的关键环节。当多个请求同时访问共享资源时,若不加以控制,很容易引发数据错乱、性能下降等问题。SemaphoreSlim作为.NET 框架中轻量级的信号量实现,为我们提供了简单而高效的并发控制方案。本文将详细介绍 SemaphoreSlim 的工作原理、使用方法以及在 Web 开发中的常见应用场景。

什么是 SemaphoreSlim

SemaphoreSlim 是.NET Framework 4.0 引入的轻量级信号量类,位于System.Threading命名空间下。它专为短期等待场景设计,相比传统的Semaphore类,具有更低的系统开销和更高的性能。

信号量的核心作用是限制同时访问某个资源的并发数量。可以将其理解为一个带计数器的门控机制:当计数器大于 0 时,允许线程进入并将计数器减 1;当计数器为 0 时,后续线程必须等待直到有线程释放资源(计数器加 1)。

与传统 Semaphore 相比,SemaphoreSlim 的优势在于:

  • 不依赖操作系统内核对象,减少了用户态与内核态之间的切换开销
  • 支持异步等待(WaitAsync方法),非常适合异步 API 开发
  • 内存占用更小,创建和销毁的成本更低
  • 支持取消令牌(CancellationToken),便于实现超时控制和操作取消

SemaphoreSlim 的核心方法

SemaphoreSlim 提供了一组简洁而强大的方法来实现并发控制,掌握这些方法是正确使用 SemaphoreSlim 的基础:

构造函数

// 初始化一个可同时允许maxCount个线程访问的信号量
public SemaphoreSlim(int initialCount);
public SemaphoreSlim(int initialCount, int maxCount);
  • initialCount:信号量的初始计数,即初始允许的并发数量
  • maxCount:信号量的最大计数,规定了允许的最大并发数量

等待方法

// 同步等待,直到信号量可用
public void Wait();
public bool Wait(int millisecondsTimeout);
public bool Wait(TimeSpan timeout);
public void Wait(CancellationToken cancellationToken);// 异步等待,适合异步方法中使用
public Task WaitAsync();
public Task<bool> WaitAsync(int millisecondsTimeout);
public Task<bool> WaitAsync(TimeSpan timeout);
public Task WaitAsync(CancellationToken cancellationToken);

等待方法的作用是申请访问权限:当调用这些方法时,线程会尝试获取信号量。如果当前计数大于 0,计数减 1 并立即返回;否则,线程会进入等待状态,直到有其他线程释放信号量或等待超时 / 被取消。

释放方法

// 释放信号量,增加计数
public void Release();
public int Release(int releaseCount);

释放方法用于归还访问权限:当线程完成对共享资源的访问后,应调用Release方法将信号量计数加 1,允许其他等待的线程获取访问权限。Release(int releaseCount)可以一次释放多个计数,但释放的总数不能超过maxCount。

基本使用模式

使用 SemaphoreSlim 的核心原则是 **"先等待,后访问,最后释放"**,通常遵循以下模式:

同步使用模式

// 创建一个最多允许3个并发访问的信号量
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3);public void AccessResource()
{try{// 等待获取信号量_semaphore.Wait();// 访问共享资源的逻辑UseSharedResource();}finally{// 释放信号量,必须放在finally中确保一定会执行_semaphore.Release();}
}

异步使用模式(推荐在 API 中使用)

private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3);public async Task AccessResourceAsync()
{try{// 异步等待获取信号量await _semaphore.WaitAsync();// 异步访问共享资源的逻辑await UseSharedResourceAsync();}finally{// 释放信号量_semaphore.Release();}
}

在 Web 开发中的常见用途

在.NET Core API 开发中,SemaphoreSlim 有多种实用场景,尤其适合处理那些需要限制并发量的操作:

1. 限制 API 接口的并发请求数

当 API 接口调用涉及到资源密集型操作(如复杂计算、大数据处理)时,无限制的并发可能导致服务器资源耗尽。使用 SemaphoreSlim 可以限制同时处理的请求数量:

[ApiController]
[Route("api/[controller]")]
public class DataProcessorController : ControllerBase
{// 限制最多5个并发请求private static readonly SemaphoreSlim _processorSemaphore = new SemaphoreSlim(5);[HttpPost("process")]public async Task<IActionResult> ProcessData([FromBody] DataRequest request){try{// 等待获取处理权限,设置5秒超时if (!await _processorSemaphore.WaitAsync(5000)){return StatusCode(StatusCodes.Status429TooManyRequests, "当前请求过多,请稍后再试");}// 处理数据var result = await DataProcessor.ProcessAsync(request);return Ok(result);}finally{_processorSemaphore.Release();}}
}

2. 保护共享资源的并发访问

当多个请求需要访问同一个共享资源(如文件、非线程安全的服务实例)时,SemaphoreSlim 可以确保资源操作的原子性:

public class FileService
{private readonly SemaphoreSlim _fileAccessSemaphore = new SemaphoreSlim(1);private readonly string _filePath = "data.json";public async Task UpdateFileAsync(string content){await _fileAccessSemaphore.WaitAsync();try{// 读取当前内容var currentContent = await File.ReadAllTextAsync(_filePath);// 处理内容var newContent = ProcessContent(currentContent, content);// 写入新内容await File.WriteAllTextAsync(_filePath, newContent);}finally{_fileAccessSemaphore.Release();}}
}

3. 控制外部服务的调用频率

调用第三方 API 时,通常会有频率限制(Rate Limiting)。使用 SemaphoreSlim 可以控制并发调用数量,避免触发限制:

public class ExternalApiClient
{private readonly HttpClient _httpClient;// 根据第三方API的限制设置并发数private readonly SemaphoreSlim _apiSemaphore = new SemaphoreSlim(10);private readonly int _apiTimeoutMs = 3000;public ExternalApiClient(HttpClient httpClient){_httpClient = httpClient;}public async Task<TResult> CallApiAsync<TResult>(string endpoint){try{// 等待获取调用权限if (!await _apiSemaphore.WaitAsync(_apiTimeoutMs)){throw new TimeoutException("等待调用第三方API超时");}// 调用外部APIvar response = await _httpClient.GetAsync(endpoint);response.EnsureSuccessStatusCode();return await response.Content.ReadFromJsonAsync<TResult>();}finally{_apiSemaphore.Release();}}
}

4. 实现分布式锁的本地补充

在分布式系统中,虽然需要分布式锁(如 Redis 锁)来跨实例同步,但结合 SemaphoreSlim 可以减少分布式锁的竞争:

public class DistributedTaskService
{private readonly IDistributedLock _distributedLock;// 每个实例最多处理2个任务,减少分布式锁竞争private readonly SemaphoreSlim _localSemaphore = new SemaphoreSlim(2);public async Task ExecuteTaskAsync(string taskId){// 先获取本地信号量,减少分布式锁的竞争await _localSemaphore.WaitAsync();try{// 再获取分布式锁using (var lockResult = await _distributedLock.AcquireLockAsync(taskId)){if (lockResult.Success){await ProcessTaskAsync(taskId);}}}finally{_localSemaphore.Release();}}
}

注意事项与最佳实践

虽然 SemaphoreSlim 使用简单,但在实际应用中仍需注意以下几点,以避免常见问题:

1. 确保正确释放信号量

最常见的错误是忘记释放信号量或在异常情况下未能释放,这会导致信号量计数永远无法恢复,最终所有请求都陷入无限等待。因此,务必将Release调用放在finally块中,确保无论是否发生异常都会执行。

2. 合理设置信号量的生命周期

在 Web 应用中,SemaphoreSlim 通常应声明为static或使用单例模式,以确保它在应用生命周期内保持状态。如果每次请求都创建新的 SemaphoreSlim 实例,将失去并发控制的作用。

// 正确:静态字段,应用域内共享
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5);// 错误:每次实例化都会创建新的信号量
public class MyController : ControllerBase
{private readonly SemaphoreSlim _badSemaphore = new SemaphoreSlim(5);// ...
}

3. 避免过度限制并发

信号量的作用是 "限制" 而非 "禁止" 并发,设置过小的并发数会导致系统吞吐量下降。应根据实际负载测试结果,结合服务器资源(CPU、内存、IO 等)合理设置最大并发数。

4. 注意异步操作中的取消机制

在异步场景中,建议使用带CancellationToken的WaitAsync重载,以便在请求被取消(如客户端断开连接)时能够及时释放资源:

// 结合HTTP请求的取消令牌
public async Task<IActionResult> MyAction()
{try{// 使用RequestAborted令牌await _semaphore.WaitAsync(ControllerContext.HttpContext.RequestAborted);// ...}finally{_semaphore.Release();}
}

5. 警惕死锁风险

当使用多个信号量时,可能会出现死锁:线程 A 持有信号量 1 并等待信号量 2,而线程 B 持有信号量 2 并等待信号量 1。避免这种情况的方法是:

  • 尽量使用单个信号量解决问题
  • 如果必须使用多个信号量,确保所有线程按相同的顺序获取它们

6. 结合限流中间件使用

对于 API 级别的全局限流,SemaphoreSlim 可以与ASP.NET Core 的限流中间件配合使用,但注意不要重复限流导致性能问题。

7. 多实例部署的局限性

需要注意的是,SemaphoreSlim 是进程内的并发控制机制,无法跨多个应用实例同步。在多服务器、多容器部署的场景中,还需要结合分布式锁(如基于 Redis 或 ZooKeeper 的实现)才能实现全局的并发控制。

总结

SemaphoreSlim 是.NET Core API 开发中处理并发控制的得力工具,它轻量高效,支持异步操作,非常适合 Web 环境下的短期等待场景。通过合理使用 SemaphoreSlim,我们可以有效限制资源访问的并发数量,保护共享资源,控制外部服务调用频率,从而提高系统的稳定性和可靠性。

使用 SemaphoreSlim 的核心在于理解 "获取 - 使用 - 释放" 的模式,并始终牢记在finally块中释放信号量。同时,也要认识到它在多实例部署中的局限性,必要时结合分布式方案进行补充。掌握这些知识,将帮助你在实际开发中更好地应对并发挑战,构建更健壮的 API 服务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/89881.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/89881.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/89881.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

板凳-------Mysql cookbook学习 (十二--------4)

11.0 概述 386 11.1 使用LOAD DATA和mysqlimport导入数据 390 首先创建 mytbl_3 表&#xff08;结构与 mytbl 相同&#xff09;&#xff1a;sql CREATE TABLE mytbl_3 LIKE mytbl;用文本编辑器&#xff08;如 Notepad&#xff09;打开 mytbl.txt&#xff0c;确保格式转换成wind…

【Git#6】多人协作 企业级开发模型

一、多人协作 1. 同一分支下的协作 目前&#xff0c;我们所完成的工作如下&#xff1a; 基本完成 Git 的所有本地库的相关操作&#xff0c;git基本操作&#xff0c;分支理解&#xff0c;版本回退&#xff0c;冲突解决等等申请码云账号&#xff0c;将远端信息clone到本地&…

C# 中的强大运算符

C# 中鲜为人知的强大运算符 C# 还提供了一些"冷门"但功能强大的运算符&#xff0c;这些运算符在特定场景下能极大简化代码并提高效率。 1. 空合并赋值运算符 ?? // 传统写法 if (variable null) {variable defaultValue; }// 使用 ?? variable ?? defaultVal…

用window字体替换zabbix 默认的字体

我们先需要在windows系统下的C:\Windows\Fonts目录&#xff0c;找到一个喜欢的字体&#xff0c;我选择的是微软雅黑。复制到其它路径下&#xff0c;选取一个msyh.ttc。到服务器上。要把msyh.ttc改为msyh.ttf才可以。不然最后中英文都不显示[roothadoop105.yinzhengjie.com ~]# …

MySQL 17 如何正确地显示随机消息?

假设有一个场景&#xff0c;一个英语学习APP首页有一个随机显示单词的功能&#xff0c;用户每次访问首页的时候&#xff0c;都会随机滚动显示三个单词。 已知表里有10000条记录&#xff0c;来看看随机选择3个单词有什么方法&#xff0c;又存在什么问题。 建表语句&#xff1a…

7-Zip 曝出两个可导致拒绝服务的中危漏洞

研究人员在全球使用最广泛的开源文件压缩软件7-Zip中新发现两个漏洞&#xff08;CVE-2025-53816和CVE-2025-53817&#xff09;。这两个漏洞影响7-Zip 25.0.0之前的所有版本&#xff0c;虽然不能实现远程代码执行&#xff0c;但可能引发内存损坏和拒绝服务&#xff08;Denial of…

史上最简单Conda+Ollama+Open-Webui安装方法!

史上最简单CondaOllamaOpen-Webui安装方法 一、安装Anaconda 1、到Anaconda官网下载conda_24.10.1 链接&#xff1a;https://repo.anaconda.com/archive/Anaconda3-2024.10-1-Windows-x86_64.exe 2.双击安装包&#xff0c;开始安装 选择All Users 切记安装路径不要选C盘&am…

Python-数据库概念-pymysql-元编程-SQLAlchemy-学习笔记

序 欠4前年的一份笔记 &#xff0c;献给今后的自己。 数据库 概念 数据库&#xff1a;按照数据结构来组织、存储、管理数据的仓库。 诞生 计算机的发明是为了做科学计算的&#xff0c;而科学计算需要大量的数据输入和输出。 早期&#xff0c;可以使用打孔卡片的孔、灯泡的亮灭来…

Linux入门篇学习——借助 U 盘或 TF 卡拷贝程序到开发板上

借助 U 盘或 TF 卡拷贝程序到开发板上我们已经学习了怎么在 ubuntu 和 windows 上互传文件&#xff0c;那么怎么把 ubuntu 或 win 上的程序拷贝到开发板呢&#xff0c;这里给大家介绍第一种方法&#xff0c;使用 U 盘或者 TF 卡来完成&#xff0c;如果大家使用的是 U 盘&#x…

【亲测有效】防检测插件playwright_stealth 2.X版本快速使用

这里写自定义目录标题核心方法apply_stealth_syncuse_sync和use_async一. playwright_stealth 2.0以上版本1.同步方法2.异步方法3.实例二.playwright_stealth 2.0以下版本playwright-stealth 是一个用于 Playwright 的库&#xff0c;旨在帮助自动化脚本避开一些检测机制&#x…

docker安装与简单项目上手

1.docker安装 系统版本为almalinux9.6 首先添加一下docker的软件安装源&#xff08;源选择的阿里云&#xff0c;只要是rhel的系统都适用&#xff0c;无论是rockylinux还是almalinux还是红帽企业版&#xff09; dnf config-manager --add-repo https://mirrors.aliyun.com/doc…

计算机网络基础:从协议到通信全解析(大致框架)

本节重点&#xff1a;1.了解网络发展背景&#xff0c;对局域网/广域网的概念有基本认识2.了解网络协议的意义&#xff0c;重点理解TCP/IP五层结构模型3.学习网络传输的基本流程&#xff0c;理解封装和解包分用一、计算机网络发展背景&#xff1a;人与人之间是需要协同工作的&am…

PDF 编辑器:多文件合并 拆分 旋转 顺序随便调 加水印 密码锁 页码背景

各位打工人、学生党们&#xff0c;你们是不是也遇到过这种情况&#xff0c;领导甩来一个PDF让你改&#xff0c;结果你捣鼓半天&#xff0c;发现这玩意儿根本动不了&#xff0c;简直想原地爆炸&#xff01;别急别急&#xff0c;今天就给你们安利一个办公软件——PDF编辑器&#…

【软件基础学习配置那些事 4-3】3ds Max2026 菜单栏常用命令-----文件、视图、编辑、工具、组

3ds Max学习的笔记小知识&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;后续都会补充添加&#xff01;&#xff01;&#xff01;&#xff01;&#xff08;个人的一些学习笔记&#xff0c;如有不对&#xff0c;欢迎订正&am…

网络爬虫的介绍

网络爬虫库网络爬虫通俗来讲就是使用代码将HTML网页的内容下载到本地的过程。爬取网页主要是为了获取网中的关键信息&#xff0c;例如网页中的数据、图片、视频等。Python语言中提供了多个具有爬虫功能的库&#xff0c;下面将具的介绍。urlib库:是Python自带的标准库&#xff0…

C# 编程实战进阶:字符串与字符串数组 (3)

目录 1、给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 2、无重复字符的最长字符串 &#xff0c;给定一个字符串 s 请你找出其中不含有重复字符的最长字符串的长度。 3、给定两个字符串 s 和 t &#xff0c;它们只包含小…

Python趣味算法:百钱百鸡问题——双重循环优化与算法效率分析

如何用Python解决中国古代数学难题?本文从暴力枚举到高效优化,带你领略算法之美,效率提升100倍! 看在每天坚持分享有趣知识的份上,点个关注吧(づ ̄ 3 ̄)づ 关注是我更新的动力 ̄︶ ̄∗ ̄︶ ̄∗) 作者会分享更多涉及到各种编程语言的有趣知识!(^∀^●)ノシ 目录 …

JAVA_TWO-初识Java2

1.IDEA管理Java程序的结构2.idea编译后的class文件在哪在工程out文件夹下。3.idea一些快捷键4.导入模块File→New→Module from Existing Sources → 添加后缀.iml文件5.注释单行注释 //多行注释 /* 注释内容1注释内容2 */文档注释 /** 注释内容 */ &#xff08;文档注释内容可…

二、Dify 版本升级教程(LInux-openeuler)

首先&#xff0c;你需要先按照好dify&#xff0c;然后才能升级&#xff0c;本文教程是基与Docker Compose 如果你还没有安装&#xff0c;可以看看这个教程。 一、Dify 私有部署、本地安装教程&#xff08;LInux-openeuler&#xff09;_dify1.5版本部署-CSDN博客 安装完成后&a…

Java 大视界 -- Java 大数据在智能安防门禁系统中的多生物特征融合识别与权限管理(280)

💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也期待你毫无保留地分享独特见解,愿我们于此携手成长,共赴新程!💖 本博…