在.NET多线程编程中,当多个线程需要同时操作共享资源时,如果不做并发控制,很容易引发资源争抢、数据不一致或者系统负载过高的问题。SemaphoreSlim是.NET框架提供的轻量级信号量工具,专门用于控制同时访问特定资源的线程数量,适合进程内的并发场景使用。

SemaphoreSlim的核心概念
SemaphoreSlim本质是一个计数信号量,它维护了两个重要的数值:初始并发数量和最大并发数量。当一个线程需要访问受保护的资源时,需要先获取信号量,如果当前可用信号量数量大于0,线程就可以继续执行,同时可用信号量数量减1;如果可用信号量数量为0,线程就会被阻塞,直到有其他线程释放信号量。
和传统的Semaphore类相比,SemaphoreSlim不需要依赖操作系统的内核对象,开销更小,性能更好,但是它不支持跨进程的同步,只适合在同一个进程内的多线程场景使用。
SemaphoreSlim的常用方法
SemaphoreSlim提供了几个核心方法用于实现并发控制,常用的方法如下:
- WaitAsync / Wait:用于获取信号量,Wait是同步方法,WaitAsync是异步方法,支持异步场景下的并发控制,调用后如果当前没有可用信号量,线程会进入等待状态。
- Release:用于释放信号量,每调用一次,可用信号量数量加1,如果有等待的线程,会唤醒其中一个等待的线程。
- AvailableWaitHandle:获取一个用于等待信号量的WaitHandle,不过SemaphoreSlim本身不推荐使用这个属性,因为它的设计初衷是轻量级实现。
使用SemaphoreSlim限制资源并发访问的示例
下面通过一个实际的例子来展示如何使用SemaphoreSlim限制对共享资源的并发访问数量,假设我们有一个需要限制同时最多3个线程访问的数据库查询资源。
同步场景示例
首先看同步方法的使用方式:
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
// 初始化SemaphoreSlim,初始并发数为3,最大并发数也为3
private static SemaphoreSlim _semaphore = new SemaphoreSlim(3, 3);
// 模拟共享资源,这里用静态变量记录当前访问的线程数
private static int _currentAccessCount = 0;
static void Main(string[] args)
{
// 启动10个线程模拟并发访问
for (int i = 0; i < 10; i++)
{
int threadId = i;
Task.Run(() => AccessResource(threadId));
}
Console.ReadLine();
}
static void AccessResource(int threadId)
{
Console.WriteLine($"线程{threadId}等待获取资源访问权限");
// 获取信号量,同步等待
_semaphore.Wait();
try
{
// 模拟资源访问操作
Interlocked.Increment(ref _currentAccessCount);
Console.WriteLine($"线程{threadId}获取到权限,当前访问线程数:{_currentAccessCount}");
Thread.Sleep(2000); // 模拟资源操作耗时
}
finally
{
// 释放信号量,一定要放在finally中避免异常导致信号量无法释放
Interlocked.Decrement(ref _currentAccessCount);
Console.WriteLine($"线程{threadId}释放资源访问权限,当前访问线程数:{_currentAccessCount}");
_semaphore.Release();
}
}
}
异步场景示例
如果是异步编程场景,可以使用WaitAsync方法避免阻塞线程:
using System;
using System.Threading;
using System.Threading.Tasks;
class AsyncProgram
{
// 初始化信号量,最多允许2个并发访问
private static SemaphoreSlim _asyncSemaphore = new SemaphoreSlim(2, 2);
static async Task Main(string[] args)
{
// 启动8个异步任务模拟并发访问
var tasks = new Task[8];
for (int i = 0; i < 8; i++)
{
int taskId = i;
tasks[i] = AsyncAccessResource(taskId);
}
await Task.WhenAll(tasks);
}
static async Task AsyncAccessResource(int taskId)
{
Console.WriteLine($"任务{taskId}等待获取资源访问权限");
// 异步获取信号量,不会阻塞线程
await _asyncSemaphore.WaitAsync();
try
{
Console.WriteLine($"任务{taskId}获取到权限,开始访问资源");
await Task.Delay(1500); // 模拟异步资源操作
Console.WriteLine($"任务{taskId}资源访问完成");
}
finally
{
// 释放信号量
_asyncSemaphore.Release();
Console.WriteLine($"任务{taskId}释放资源访问权限");
}
}
}
使用注意事项
在使用SemaphoreSlim的时候需要注意以下几点:
- 获取信号量的操作一定要和释放操作配对,建议把释放操作放在try-finally块中,避免因为代码异常导致信号量无法释放,引发死锁问题。
- 初始化的时候初始并发数和最大并发数可以根据实际需求设置,如果需要动态调整最大并发数,可以调用Release方法传入释放的数量,不过要注意不要超过初始化时设置的最大并发数上限。
- SemaphoreSlim不支持跨进程使用,如果有跨进程的并发控制需求,需要使用传统的Semaphore类。
- 如果不需要异步支持,优先使用同步的Wait方法,减少异步状态机的开销。
适用场景总结
SemaphoreSlim适合以下场景使用:
- 限制对数据库、文件、第三方接口等共享资源的并发访问数量,避免资源被过度占用。
- 控制线程池任务的并发执行数量,避免大量任务同时执行导致系统负载过高。
- 实现限流功能,比如限制某个接口的每秒请求处理数量。
通过合理使用SemaphoreSlim,可以有效控制多线程场景下的资源访问并发度,提升系统的稳定性和性能。
SemaphoreSlim并发控制资源访问限制.NET多线程修改时间:2026-07-08 04:42:25