c# ReaderWriterLock 和 ReaderWriterLockSlim 的废弃与推荐

15次阅读

ReaderWriterLock 已过时,应改用 ReaderWriterLockSlim;前者基于内核对象、开销大、无超时返回机制、不支持锁升级,后者为用户态高性能实现,支持超时、升级/降级、严格配对释放及递归策略控制。

c# ReaderWriterLock 和 ReaderWriterLockSlim 的废弃与推荐

ReaderWriterLock 已被明确标记为过时

ReaderWriterLock.net Framework 2.0 中引入,但自 .NET Framework 3.5 起就被官方弃用;它未被包含在 .NET Core / .NET 5+ 的默认 API 集中,且在 netstandard2.0 及更高版本中仅作为兼容性类型存在(需引用 System.Threading 程序集)。微软文档和源码注释中均标注其“obsolete”,不建议新项目使用。

  • 它基于 win32 内核同步对象(如 CreateEvent),开销大、响应慢、易死锁
  • 没有超时重载的 tryEnterReadLock(int) 等方法,超时失败时抛出 applicationException,而非返回 false,难以安全捕获和处理
  • 不支持升级锁(upgradable read lock),也无法降级,业务逻辑受限

ReaderWriterLockSlim 是唯一推荐的现代读写锁

ReaderWriterLockSlim 是 .NET Framework 3.5 引入的托管替代方案,专为高性能、低开销、高可控性设计。它完全在用户态实现(无内核切换),支持完整的超时控制、升级/降级语义,并被深度集成进 .NET Core / .NET 5+ 的基础类库中。

  • 所有核心方法都有 Try 版本:TryEnterReadLock(int)TryEnterWriteLock(int)TryEnterUpgradeableReadLock(int),失败直接返回 false,避免异常干扰流程
  • 支持 EnterUpgradeableReadLock()EnterWriteLock() 升级路径(注意:必须在同一线程内完成,且不能嵌套或跨 await)
  • 构造函数可传入 LockRecursionPolicy(默认 NoRecursion),显式禁止递归加锁,提前暴露线程安全漏洞
  • 释放锁必须严格配对:ExitReadLock() / ExitWriteLock() / ExitUpgradeableReadLock(),否则会引发 SynchronizationLockException

常见误用:把 ReaderWriterLockSlim 当成 lock 用

很多人照搬 lock 的写法,只在写操作加锁、读操作裸奔——这在 ReaderWriterLockSlim 下是严重错误,会导致脏读或 KeyNotFoundException(尤其在 Dictionary 上)。

  • 所有访问共享资源的代码路径,无论读或写,都必须包裹对应锁:读走 EnterReadLock(),写走 EnterWriteLock(),升级写走 EnterUpgradeableReadLock() + EnterWriteLock()
  • 忘记 ExitXxxLock() 或在异常路径中遗漏释放,将导致锁永久占用,后续线程无限等待(表现为 CPU 低但请求卡死)
  • 不要在 async 方法中持有 ReaderWriterLockSlim 锁——它**不是 await 安全的**。若需异步等待,请改用 AsyncReaderWriterLock(如 Nito.AsyncEx 提供)

性能与场景选择:不是所有读多写少都该用它

ReaderWriterLockSlim 的优势只在「读远多于写」且「单次读操作耗时明显」时才显著。若读操作极轻(如简单字段访问)、或并发度不高(lock 反而更高效、更简洁。

  • 基准测试显示:在高争用(大量写)下,ReaderWriterLockSlim 性能可能低于 Monitor(即 lock
  • 若数据结构本身已线程安全(如 ConcurrentDictionaryConcurrentQueue),优先用它们,比手写锁更可靠、更免维护
  • 真正需要读写分离语义时(例如缓存命中率统计、配置热更新、状态快照读取),再引入 ReaderWriterLockSlim,并确保锁粒度合理(宁拆勿合)
private static readonly Dictionary _cache = new(); private static readonly ReaderWriterLockSlim _rwLock = new(LockRecursionPolicy.NoRecursion);  public static bool TryGetValue(string key, out double value) {     _rwLock.EnterReadLock();     try     {         return _cache.TryGetValue(key, out value);     }     finally     {         _rwLock.ExitReadLock();     } }  public static void SetValue(string key, double value) {     _rwLock.EnterWriteLock();     try     {         _cache[key] = value;     }     finally     {         _rwLock.ExitWriteLock();     } }

升级锁、跨线程释放、混用 async/await —— 这些地方不报错,但会在某个高并发时刻突然崩掉,而且很难复现。别等线上报警才查。

text=ZqhQzanResources