Skip to content

C# 提供了丰富的锁机制和同步原语,从简单的 lock 到复杂的 Barrier,每种都针对特定并发问题设计。以下是完整的分类、使用场景和痛点解决对照表:


一、基础锁机制

锁类型适用场景解决的痛点关键特性
lock (Monitor)保护临界区(如修改共享变量、集合)防止数据竞争,避免多线程同时修改导致的不一致最常用,语法糖,只能锁引用类型,同线程可重入
Mutex跨进程同步(如防止应用多开)解决进程级别的资源竞争内核对象,重量级,支持跨进程,可命名
Semaphore/Slim限制并发数量(如数据库连接池)控制同时访问某资源的线程数,避免资源耗尽Slim版轻量、支持异步,Semaphore可跨进程

二、读写锁优化

锁类型适用场景解决的痛点关键特性
ReaderWriterLockSlim读多写少场景(如缓存系统)解决读并发性能问题,避免读读互斥允许多读单写,可升级锁,比旧版性能高5倍

示例:配置中心服务,读操作占95%,写操作少。


三、高性能/特殊场景锁

锁类型适用场景解决的痛点关键特性
SpinLock超短临界区(如简单计数器)避免上下文切换开销(用户态锁)自旋等待,不阻塞线程,锁持有时间<1μs才划算
Interlocked原子操作(如自增、CAS)无需锁的开销实现线程安全,解决轻量级同步硬件级原子指令,性能最高,只能操作单个变量

性能对比Interlocked > SpinLock > lock > Mutex


四、线程协调与信号

锁类型适用场景解决的痛点关键特性
ManualResetEvent线程持续通知(如全局开关)解决线程间信号传递,控制多个线程启停信号手动复位,需Reset()关闭
AutoResetEvent线程单次通知(如生产者-消费者)同ManualResetEvent,但自动复位信号自动复位,类似门票机制
CountdownEvent等待多个线程完成(如并行计算汇总)解决"所有任务完成后再继续"的问题初始化计数器,Signal()递减,Wait()阻塞
Barrier分阶段并行(如MapReduce多阶段)解决"所有线程到达某点后再一起继续"的协调问题可设置阶段回调,适合复杂并行算法

五、异步编程专用

锁类型适用场景解决的痛点关键特性
SemaphoreSlim.WaitAsync()async/await中的同步避免阻塞线程池线程,提升异步代码性能支持异步等待,不阻塞调用线程
AsyncLock (第三方)纯异步方法的临界区保护解决lock在异步代码中导致的死锁风险需配合using,如 Nito.AsyncEx.AsyncLock

六、现代替代方案

机制适用场景解决传统锁的痛点
ConcurrentDictionary等并发集合高频读写集合内部优化锁粒度,比手动lock性能高3-5倍
Lazy< T >延迟初始化单例内置双重检查锁,无需手写复杂模式
volatile状态标志(如退出标记)禁用CPU缓存,保证可见性,不保证原子性

选型决策树

plaintext
需要跨进程? → 是 → Mutex
           ↓ 否
需要读写分离? → 是 → ReaderWriterLockSlim
             ↓ 否
需要限制并发数? → 是 → SemaphoreSlim
               ↓ 否
是异步代码? → 是 → SemaphoreSlim.WaitAsync()
           ↓ 否
临界区<1μs? → 是 → SpinLock
           ↓ 否
简单计数器? → 是 → Interlocked
           ↓ 否
默认 → lock

核心警告 ⚠️

  • 避免锁字符串:字符串驻留机制会导致跨进程的意外锁定
  • 避免lock(this):外部也可能锁定该对象,导致死锁风险
  • 异步代码别用locklockasync/await中会导致死锁或线程池饥饿
  • 性能陷阱Mutexlock慢100倍,仅在跨进程时使用

总结:默认用 lock,读多写少用 ReaderWriterLockSlim,异步用 SemaphoreSlim.WaitAsync(),跨进程用 Mutex,原子操作用 Interlocked