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):外部也可能锁定该对象,导致死锁风险
- 异步代码别用lock:
lock在async/await中会导致死锁或线程池饥饿 - 性能陷阱:
Mutex比lock慢100倍,仅在跨进程时使用
总结:默认用
lock,读多写少用ReaderWriterLockSlim,异步用SemaphoreSlim.WaitAsync(),跨进程用Mutex,原子操作用Interlocked。
