C# Semaphore与SemaphoreSlim区别小结

2025-12-01 18:50

本文主要是介绍C# Semaphore与SemaphoreSlim区别小结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《C#Semaphore与SemaphoreSlim区别小结》本文主要介绍了C#Semaphore与SemaphoreSlim区别小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的...

C#(.NET)中,SemaphoreSemaphorewww.chinasem.cnSlim 都用于控制同时访问某一资源或池的线程数量(即“信号量”机制),但它们在实现方式、性能、功能和适用场景上有显著区别。

下面从多个维度详细对比两者,并附使用示例。

一、核心区别概览

特性SemaphoreSemaphoreSlim
命名空间System.ThreadingSystem.Threading
底层实现内核模式(Windows 内核信号量对象)用户模式 + 混合模式(优先自旋,必要时用内核事件)
跨进程支持✅ 支持(可通过名称创建命名信号量)❌ 不支持
异步支持❌ 无 WaitAsync()✅ 支持 WaitAsync()
性能较低(每次 Wait/Release 涉及内核切换)较高(短竞争无内核开销)
适用场景跨进程同步、长时间等待进程内同步、高性能、异步编程
是否可重入
释放行为必须由获取线程释放(但无所有权概念)同左

二、详细对比说明

1.跨进程支持

Semaphore

// 进程 A
var sem = new Semaphore(2, 2, "MyGlobalSemaphore");

// 进程 B
var sem = Semaphore.OpenExisting("MyGlobalSemaphore");

适用于多个进程共享有限资源(如硬件设备、全局连接池)。

  • 可创建命名信号量,供多个进程共享。

SemaphoreSlim

  • 仅限当前进程内使用,无法跨进程。
  • 内部使用 ManualResetEventSlim 或自旋,无内核对象名称。

2.异步支持(关键区别!)

SemaphoreSlim 提供 WaitAsync() 方法,完美支持 async/await

private static SemaphoreSlim _sem = new SemaphoreSlim(3);

public async Task ProcessAsync()
{
    await _sem.WaitAsync(); // 异步等待,不阻塞线程
    try
    {
        await CallExternalApiAsync(); // 模拟 I/O 操作
    }
    finally
    {
        _sem.Release();
    }
}

✅ 非常适合 Web API、高并发 I/O 场景(如限流)。

Semaphore 只有同步方法 WaitOne(),在异步上下文中会阻塞线程,导致线程池饥饿:

// ❌ 不推荐在 async 方法中使用
sem.WaitOne(); // 阻塞当前线程!

3.性能差异

  • SemaphoreSlim

    • 在无竞争或轻度竞争时,完全在用户态运行,无系统调用。
    • 即使有竞争,也先自旋(SpinWait),失败后才使用轻量内核事件。
    • 延迟低、吞吐高
  • Semaphore

    • 每次 WaitOne() / Release() 都触发内核模式切换(约 1000~3000 纳秒开销)。
    • 适合低频、长时间持有的场景。

性能测试表明:在高频短临界区场景,SemaphoreSlim 比 Semaphore 快 5~10 倍以上。

4.API 差异

功能SemaphoreSemaphoreSlim
构造函数Semaphore(initialCChina编程ount, maximumCount, name?)SemaphoreSlim(initialCount, maxCount?)
等待WaitOne(), WaitOne(timeout)Wait(), Wait(timeout), WaitAsync(), WaitAsync(timeout)
释放Release(), Release(count)Release(), Release(count)
打开现有(跨进程)OpenExisting(name)❌ 不支持

三、使用示例对比

✅ 场景:限制并发 HTTP 请求(推荐用SemaphoreSlim)

// 使用 SemaphoreSlim(支持异步)
private static readonly SemaphoreSlim _throttle = new SemaphoreSlim(5, 5);

public async Task<string> FetchDataAsync(string url)
{
    await _throttle.WaitAsync(); // 异步等待,不占线程
    try
    {
        using var client = new HttpClient();
      http://www.chinasem.cn  return await client.GetStringAsync(url);
    }
    finallphpy
    {
        _throttle.Release();
    }
}

✅ 高效、不阻塞线程池线程,适合 ASP.NET Core 等高并发环境。

✅ 场景:两个进程共享最多 2 个数据库连接(必须用Semaphore)

进程 A:

var sem = new Semaphore(2, 2, "Global\\DBConnectionPool");
sem.WaitOne();
// 使用数据库连接...
sem.Release();

进程 B:

var sem = Semaphore.OpenExisting("Global\\DBConnectionPool");
sem.WaitOne();
// 使用数据库连接...
sem.Release();

✅ 只有 Semaphore 能跨进程协调资源。

四、如何选择?

需求推荐类型
进程内同步 + 异步支持✅ SemaphoreSlim
高性能、低延迟✅ SemaphoreSlim
跨进程同步✅ Semaphore
长时间持有信号量⚠️ 两者皆可,但 Semaphore 更稳定
Web 服务限流、API 调用控制✅ SemaphoreSlim(配合 WaitAsync)
桌面应用多实例共享资源✅ Semaphore(命名)

五、注意事项

1.SemaphoreSlim不是线程安全的“计数器”

  • 它只控制进入数量,不保证操作原子性。
  • 临界区内仍需其他同步机制(如 lock)保护共享数据。

2.避免在SemaphoreSlim中阻塞线程

// ❌ 错误:在 WaitAsync 后又同步阻塞
await _sem.WaitAsync();
Thread.Sleep(1000); // 浪费线程!应使用 await Task.Delay(1000)

3.Release()调用次数不能超过Wait()

  • 否则会抛出 SemaphoreFullException
  • 建议始终用 try/finally 包裹。

✅ 总结

对比项SemaphoreSemaphoreSlim
定位通用、跨进程高性能、进程内
灵魂特性跨进程异步支js持(WaitAsync)
现代 .NET 首选仅当需要跨进程时✅ 绝大多数场景

默认选择 SemaphoreSlim;只有需要跨进程时才用 Semaphore。

到此这篇关于C# Semaphore与SemaphoreSlim区别小结的文章就介绍到这了,更多相关C# Semaphore SemaphoreSlim内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!

这篇关于C# Semaphore与SemaphoreSlim区别小结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1156325

相关文章

C#借助Spire.XLS for .NET实现在Excel中添加文档属性

《C#借助Spire.XLSfor.NET实现在Excel中添加文档属性》在日常的数据处理和项目管理中,Excel文档扮演着举足轻重的角色,本文将深入探讨如何在C#中借助强大的第三方库Spire.... 目录为什么需要程序化添加Excel文档属性使用Spire.XLS for .NET库实现文档属性管理Sp

Spring配置扩展之JavaConfig的使用小结

《Spring配置扩展之JavaConfig的使用小结》JavaConfig是Spring框架中基于纯Java代码的配置方式,用于替代传统的XML配置,通过注解(如@Bean)定义Spring容器的组... 目录JavaConfig 的概念什么是JavaConfig?为什么使用 JavaConfig?Jav

Spring Boot Interceptor的原理、配置、顺序控制及与Filter的关键区别对比分析

《SpringBootInterceptor的原理、配置、顺序控制及与Filter的关键区别对比分析》本文主要介绍了SpringBoot中的拦截器(Interceptor)及其与过滤器(Filt... 目录前言一、核心功能二、拦截器的实现2.1 定义自定义拦截器2.2 注册拦截器三、多拦截器的执行顺序四、过

C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解

《C++,C#,Rust,Go,Java,Python,JavaScript的性能对比全面讲解》:本文主要介绍C++,C#,Rust,Go,Java,Python,JavaScript性能对比全面... 目录编程语言性能对比、核心优势与最佳使用场景性能对比表格C++C#RustGoJavapythonjav

C# 预处理指令(# 指令)的具体使用

《C#预处理指令(#指令)的具体使用》本文主要介绍了C#预处理指令(#指令)的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录1、预处理指令的本质2、条件编译指令2.1 #define 和 #undef2.2 #if, #el

C#实现将Excel工作表拆分为多个窗格

《C#实现将Excel工作表拆分为多个窗格》在日常工作中,我们经常需要处理包含大量数据的Excel文件,本文将深入探讨如何在C#中利用强大的Spire.XLSfor.NET自动化实现Excel工作表的... 目录为什么需要拆分 Excel 窗格借助 Spire.XLS for .NET 实现冻结窗格(Fro

C# List.Sort四种重载总结

《C#List.Sort四种重载总结》本文详细分析了C#中List.Sort()方法的四种重载形式及其实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录1. Sort方法的四种重载2. 具体使用- List.Sort();- IComparable

C#中Trace.Assert的使用小结

《C#中Trace.Assert的使用小结》Trace.Assert是.NET中的运行时断言检查工具,用于验证代码中的关键条件,下面就来详细的介绍一下Trace.Assert的使用,具有一定的参考价值... 目录1、 什么是 Trace.Assert?1.1 最简单的比喻1.2 基本语法2、⚡ 工作原理3

C#中DateTime的格式符的实现示例

《C#中DateTime的格式符的实现示例》本文介绍了C#中DateTime格式符的使用方法,分为预定义格式和自定义格式两类,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值... 目录DateTime的格式符1.核心概念2.预定义格式(快捷方案,直接复用)3.自定义格式(灵活可控

C# IPAddress 和 IPEndPoint 类的使用小结

《C#IPAddress和IPEndPoint类的使用小结》本文主要介绍了C#IPAddress和IPEndPoint类的使用小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定... 目录一、核心作用网络编程基础类二、IPAddress 类详解三种初始化方式1. byte 数组初始化2. l