SemaphoreSlim是一个用于同步和限制并发访问的类,和它类似的还有Semaphore,只是SemaphoreSlim更加的轻量、高效、好用。今天说说它,以及如何使用,在什么时候去使用,使用它将会带来什么优势。
代码的业务是:
在多线程下进行数据的统计工作,简单点的说就是累加数据。
1.首先我们建立一个程序
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static int a = 0; static async Task Main( string [] args) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Task t1 = Task.Run(() => { A(); }); Task t2 = Task.Run(() => { B(); }); await Task.WhenAll(t1, t2); stopwatch.Stop(); Console.WriteLine( "总时间:" + stopwatch.ElapsedMilliseconds); Console.WriteLine( "总数:" + a); Console.ReadLine(); } private static void A() { for ( int i = 0; i < 100_0000; i++) { a++; } } private static void B() { for ( int i = 0; i < 100_0000; i++) { a++; } } } } |
2.运行结果
此时需要多运行几次,会发现,偶尔出现运行的结果不一样,这就是今天的问题
3.分析结果
从结果看,明显错误了,正确答案是:200 0000,但是第二次的结果是131 6465。我们的业务就是开启2个线程,一个A方法,一个B方法,分别对a的数据进行累加计算。那么为什么造成这样的结果呢?
造成这样的原因就是多线程的问题。
解决方法一:
1.使用lock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static int a = 0; static object o = new object (); static async Task Main( string [] args) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Task t1 = Task.Run(() => { A(); }); Task t2 = Task.Run(() => { B(); }); await Task.WhenAll(t1, t2); stopwatch.Stop(); Console.WriteLine( "总时间:" + stopwatch.ElapsedMilliseconds); Console.WriteLine( "总数:" + a); Console.ReadLine(); } private static void A() { for ( int i = 0; i < 100_0000; i++) { lock (o) { a++; } } } private static void B() { for ( int i = 0; i < 100_0000; i++) { lock (o) { a++; } } } } } |
2. lock的结果
当我们增加lock后,不管运行几次,结果都是正确的。
解决方法二:
1.使用SemaphoreSlim
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static int a = 0; static object o = new object(); static SemaphoreSlim semaphore = new SemaphoreSlim(1); //控制访问线程的数量 static async Task Main(string[] args) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Task t1 = Task.Run(() => { A(); }); Task t2 = Task.Run(() => { B(); }); await Task.WhenAll(t1, t2); stopwatch.Stop(); Console.WriteLine("总时间:" + stopwatch.ElapsedMilliseconds); Console.WriteLine("总数:" + a); Console.ReadLine(); } private static void A() { for (int i = 0; i < 100_0000; i++) { semaphore.Wait(); //lock (o) //{ a++; //} semaphore.Release(); } } private static void B() { for (int i = 0; i < 100_0000; i++) { semaphore.Wait(); //lock (o) //{ a++; //} semaphore.Release(); } } } }
2.SemaphoreSlim的效果
当我们增加SemaphoreSlim后,不管运行几次,结果都是正确的。
4.我们对比方法一和方法二发现,他们的结果都是一样的,但是lock似乎比SemaphoreSlim更加的高效,是的,lock解决此业务的确比SemaphoreSlim高效。但是lock能干的事,SemaphoreSlim肯定能干,SemaphoreSlim能干的事,lock不一定能干。
5.SemaphoreSlim的使用
SemaphoreSlim使用的范围非常的广,可以限制访问资源的线程数,例如限制一个资源最多5个线程可以同时访问
using System.Threading; class Program { static SemaphoreSlim semaphore = new SemaphoreSlim(5); // 允许最多5个线程同时访问资源 static void Main() { for (int i = 0; i < 10; i++) { Task.Run(DoWork); } Console.ReadLine(); } static async Task DoWork() { await semaphore.WaitAsync(); // 等待许可 try { Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 开始工作"); await Task.Delay(1000); // 模拟耗时操作 Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 结束工作"); } finally { semaphore.Release(); // 释放许可 } } }
此时DoWork()这个方法,最多同时只有5个线程访问,当改成1个,就是按照顺序进行了,和lock的使用是一样的
6.总结
如果需要确保同一时间只有一个线程访问某资源(此案例指的就是变量a),那么可以使用Lock,也可以使用SemaphoreSlim;如果需要控制同时访问资源的线程数量,并且需要更复杂的信号量操作,那么可以使用SemaphoreSlim。总之,使用Lock还是SemaphoreSlim,都是根据具体业务而定。
拓展:
当我们在第1步,只需要增加一句话,不增加lock和SemaphoreSlim,依然可以使得计算的结果准确,那就是增加
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static int a = 0; static async Task Main(string[] args) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Task t1 = Task.Run(() => { A(); }); Task t2 = Task.Run(() => { B(); }); await Task.WhenAll(t1, t2); stopwatch.Stop(); Console.WriteLine("总时间:" + stopwatch.ElapsedMilliseconds); Console.WriteLine("总数:" + a); Console.ReadLine(); } private static void A() { for (int i = 0; i < 10_0000; i++) { a++; Console.WriteLine(a); } } private static void B() { for (int i = 0; i < 10_0000; i++) { a++; Console.WriteLine(a); } } } }
效果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南