当前位置: 首页 > news >正文

推荐一种并发线程中资源同步常用方法

在实际应用开发中,为了提高效率,一些大的任务会被拆成很多小的子任务,然后再将任务按照先后顺序进行排列组合,而某些可以同时执行的任务,就会被安排成并行执行,进而就会用到多线程去处理;这些并发线程,有时会需要使用同一种资源,且这种资源在同一时刻也只能供少量或单一线程使用,这种资源被称为临界资源。那如何才能保证在并发线程中,各个线程都能有条不紊的使用临界资源呢?我们需要给临界资源一个信号量(Semaphore),当资源正在被占用时,告诉其他后面的线程,需要等待前面的线程使用资源结束,才能接着使用,总而言之,就是需要排队使用资源。

今天我们以一些简单的小例子,简述在.NET开发中信号量的使用方法与应用,仅供学习分享使用,如有不足之处,还请指正。

 

image

 

什么是信号量?

 

在 C# 中,信号量(Semaphore)是一种用于线程同步的机制,能够控制对共享资源的访问。它的工作原理是通过维护一个计数器来控制对资源的访问次数。它常用于限制对共享资源(如数据库连接池、文件系统、网络资源等)的并发访问。

信号量理解起来有三点核心要素:

  1. 计数:信号量的核心是一个计数器,表示当前资源的可用数量;即总资源数-正在被使用资源数=剩余可用数量。
  2. 等待:当线程请求资源时,此次如果计数器大于0,则线程可以继续执行,同时计数器减1;如果计数器等于0,则线程被阻塞直至其他线程释放资源,即有线程增加计数器的值;
  3. 释放:当线程使用完资源后,则需要释放信号量,同时计数器加1,并唤醒其他等待的线程;

通过信号量的定义和核心要素,以及实现原理,可以推断出它的应用场景,就是控制对共享临界资源的使用,避免出现过度使用资源的现象出现。

 

在.NET开发中,基于C#语言,有两种信号量实现方式,具体如下所示:

  • Semaphore:是基于系统内核实现,属于内核级别同步,支持跨进程资源同步,因此性能较低,内存占用较大;它可以一次释放多个信号量,但是没有提供原生的异步支持;
  • SemaphoreSlim:是用户级别同步,并不依赖系统内核,因此不支持跨进程资源同步,因此性能更高,内存占用更低;它一次只能释放一个信号量,但是提供了原生异步支持;

 

具体对比如下图所示:

image

 

Semaphore实例

 

Semaphore主要方法有以下几个:

  • 构造方法,Semaphore提供在构造方法有3个,可以根据需要用来设置初始信号数量,最大信号数量,信号量名称,out修饰的是否创建新的信号。如果不需要跨进程,则采集两个默认两个参数在构造函数即可。
  • WaitOne,等待方法用于等待一个信号,此方法接收一个参数用于设置超时时间,它返回一个bool值,true表示接收到信号,false表示没有接收到信号。
  • Release,释放资源方法,默认释放一个资源的占用。此方法接收一个参数用于设置释放占用资源的数量。

 

应用示例:定义一个共享资源信号量,同一时刻允许2个线程使用,最大线程数量为5,则采用默认构造函数并指定初始值,最大值即可,如下所示:

 

namespace Okcoder.DemoSemaphore
{public class Test{private readonly Semaphore semaphore;public Test(){semaphore = new Semaphore(2, 5);}/// <summary>/// Work/// </summary>/// <param name="id"></param>public void Work(int id){try{Console.WriteLine($"id = {id} 正在等待进入 working area.");semaphore.WaitOne();Console.WriteLine($"id = {id} 进入了working area.");//do somethingThread.Sleep(3000);}catch{throw;}finally{Console.WriteLine($"id = {id} 离开了working area.");semaphore.Release();}}}
}

 

当我们创建5个线程,同时调用资源时,也只允许同一时刻2个线程使用,如下所示:

 

using Okcoder.DemoSemaphore;
using System;namespace ConsoleAppWithoutTopLevelStatements
{class Program{static void Main(string[] args){Console.WriteLine("Hello, World!");Test test = new Test();for (int i = 0; i < 5; i++){int id = i;Task.Run(() =>{test.Work(id);});}Console.ReadKey();}}
}

 

运行实例,效果如下所示:

 

image

 

命名信号量

 

Semaphore是内核级别同步,在构造函数中,可以为信号量指定名称,这样就可以跨进程获取信号量,并进行操作。如下所示:

 

namespace Okcoder.DemoSemaphore
{public class Test3{private readonly Semaphore semaphore;public Test3(){semaphore = new Semaphore(1, 1,"okcoder");}/// <summary>/// Work/// </summary>/// <param name="id"></param>public void Work(int id){Console.WriteLine($"id = {id} 正在等待进入 working area.");semaphore.WaitOne();Console.WriteLine($"id = {id} 进入了working area.");}}
}

 

同样运行5个线程调用方法,如下所示:

 

using Okcoder.DemoSemaphore;
using System;namespace ConsoleAppWithoutTopLevelStatements
{class Program{static void Main(string[] args){Console.WriteLine("Hello, World!");Test3 test = new Test3();for (int i = 0; i < 5; i++){int id = i;Task.Run(() =>{test.Work(id);});}Console.ReadKey();}}
}

 

在上述示例中,指定了一个初始信号数量为1,最大信号量为1,名称为okcoder的信号量,接下在另外一个程序中通过Semaphore.OpenExisting方法进行获取信号量,并操作信号量的释放,如下所示:

 

using System;namespace ConsoleAppWithoutTopLevelStatements2
{class Program{static void Main(string[] args){Console.WriteLine("输入回车键,释放信号量");Semaphore semaphore = Semaphore.OpenExisting("okcoder");for (int i = 0; i < 5; i++){if (Console.ReadKey().Key == ConsoleKey.Enter){semaphore.Release();Console.WriteLine("释放一个信号量");}}Console.WriteLine("信号量已释放完成,按任意键退出");Console.ReadKey();}}
}

 

运行实例如下所示:

 

GIF 2025-12-12 22-21-38

 

在上述示例中,每输入一个Enter键,就释放一个信号量,原进程就允许进入一个资源,说明Semaphore可以被跨进程获取和操作,进而证明它是系统内核级别的同步。

 

SemaphoreSlim实例

 

SemaphoreSlim是Semaphore的一个轻量级实现,它是用户级别的,它允许在同一个进程内各线程之间进行资源的同步。SemaphoreSlim只需要指定初始线程数量,最大线程数量即可,不需要指定信号量的名称。

SemaphoreSlim主要方法有以下几个:

  • 构造方法,SemaphoreSlim提供在构造方法有2个,可以根据需要用来设置初始信号数量,最大信号数量。
  • Wait,等待方法用于等待一个信号,此方法接收一个参数用于设置超时时间,它返回一个bool值,true表示接收到信号,false表示没有接收到信号。
  • WaitAsync,支持Wait信号的异步调用。
  • Release,释放资源方法,默认释放一个资源的占用。此方法接收一个参数用于设置释放占用资源的数量。

说明:如果不需要跨进程处理资源同步的时候,SemaphoreSlim才是最佳选择。

 

应用示例:定义一个共享资源信号量,同一时刻允许2个线程使用,最大线程数量为5,则采用默认构造函数并指定初始值,最大值即可,如下所示:

 

namespace Okcoder.DemoSemaphore
{public class Test2{private readonly SemaphoreSlim semaphoreSlim;public Test2(){semaphoreSlim = new SemaphoreSlim(2, 5);}/// <summary>/// Work/// </summary>/// <param name="id"></param>public void Work(int id){try{Console.WriteLine($"id = {id} 正在等待进入 working area.");semaphoreSlim.Wait();Console.WriteLine($"id = {id} 进入了working area.");//do somethingThread.Sleep(3000);}catch{throw;}finally{Console.WriteLine($"id = {id} 离开了working area.");semaphoreSlim.Release();}}}
}

 

当我们创建5个线程,同时调用资源时,也只允许同一时刻2个线程使用,如下所示:

 

using Okcoder.DemoSemaphore;
using System;namespace ConsoleAppWithoutTopLevelStatements
{class Program{static void Main(string[] args){Console.WriteLine("Hello, World!");Test2 test = new Test2();for (int i = 0; i < 5; i++){int id = i;Task.Run(() =>{test.Work(id);});}Console.ReadKey();}}
}

 

运行实例,效果如下所示:

 

image

可以看到,每次进入共享资源的顺序都不同,但同一时刻允许进入的数量是一样的。

 

以上就是《推荐一种并发线程中资源同步常用方法》的全部内容,旨在抛砖引玉,一起学习,共同进步。

http://www.jsqmd.com/news/89300/

相关文章:

  • 24、文本处理工具的使用与技巧
  • C#+VisionMaster联合开发(七)_通讯管理
  • Flutter 2025:从架构革命到商业落地,全面解析跨平台开发的“黄金时代”
  • 狮子老虎图像识别分类基于YOLO11-FasterNet实现含Python源码_268期
  • 2025 年 12 月胰岛素泵厂家最新推荐,如意泵,贴敷式与便携式二合一,全年龄段贴敷泵胰岛素泵公司选择指南 - 品牌鉴赏师
  • Claude vs ChatGPT vs Gemini: 기능 비교, 사용 경험, 적합 인군
  • 20、文件搜索、压缩与归档操作指南
  • 当AI芯片不再性感:博通的高增长,为何成了催命符?
  • 21、数据存档、备份与正则表达式应用全解析
  • JoyAgent-JDGenie项目业务逻辑梳理
  • 图论入门:从存储结构到DFS/BFS遍历,零基础也能看懂的实战教程
  • ASUS路由器更新Merlin固件
  • 图论入门:从存储结构到DFS/BFS遍历,零基础也能看懂的实战教程 接上文
  • 当传统水塔遇上PLC自动化:博途仿真实战
  • 房地产公司组织结构图在线设计 项目开发团队层级
  • Vibe Coding:AI驱动的编程新范式
  • 直接开整!咱今天唠唠怎么用维纳过程预测设备寿命,手把手带代码那种。准备好你的Python环境,咱们从数据生成一路干到参数更新
  • WebRTC架构详解:实现浏览器实时通信的技术核心
  • 贾子智慧商业化——现代创业致胜完整框架 | Kucius Wisdom Commercialization— A Complete Framework for Modern Entrepreneure
  • 010.只是read( )、wr( )
  • 量化交易的思路
  • Spring Boot 3 + JDK 21 项目中从 Swagger 2 升级到 OpenAPI 3.0(Knife4j)的完整实践指南——以苍穹外卖项目为例
  • JS核心语法
  • WebSocket架构详解:从协议原理到企业级应用实践
  • JS函数语法(重点)
  • 抖音直播卖货起号第一天微付费模式怎么投放
  • 如何选择专业的工程照明公司?
  • 数字电路模拟程序--大作业中期总结
  • C语言复习相关
  • get+二分