ReaderWriterLockSlim 不是 lock 的平替,仅适用于读远多于写且读操作极轻量的场景;误用会导致性能更差、死锁或句柄泄漏。

直接说结论:ReaderWriterLockSlim 不是 lock 的平替,只在「读远多于写 + 读操作极轻量」时才值得用;用错场景、写错结构,性能比 lock 还差,且极易死锁或泄漏。
EnterReadLock() 调两次就卡死?默认禁止递归是硬阻塞
这不是“提示你错了”,而是线程立刻挂起、无异常、无日志、无法恢复。原因:默认构造(new ReaderWriterLockSlim())等价于 new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion),内部根本不允许同一线程重复进入读锁。
- 常见触发点:嵌套方法调用中无意多次
EnterReadLock(),比如 A 方法持读锁调 B 方法,B 又自己加一次 - 别依赖无参构造——未来 .NET 版本可能改默认策略;显式传
LockRecursionPolicy.NoRecursion或SupportsRecursion才可控 - 真需要递归读?代价高:逻辑变复杂、升级锁路径更易出错、死锁面更大;优先收窄锁粒度,把
EnterReadLock()限制在单个方法内 +try/finally配对
TryEnterWriteLock 超时后千万别调 ExitWriteLock
TryEnterWriteLock(int) 返回 false 表示锁根本没拿到,此时线程不在写模式,调 ExitWriteLock() 必抛 SynchronizationLockException。
- 典型误用:
if (!rwLock.TryEnterWriteLock(100)) { /* 处理失败 */ }后直接写rwLock.ExitWriteLock()—— 这段代码永远不该出现在if外 - 正确结构必须严格配对:
if (rwLock.TryEnterWriteLock(100)) { try { /* 写操作 */ } finally { rwLock.ExitWriteLock(); } } - 超时值别设
-1(等价于无限等待);生产环境建议100–500ms,避免一个慢写拖垮所有读请求
UpgradeableReadLock 不是“读完顺手改”,而是高风险竞态通道
EnterUpgradeableReadLock() → EnterWriteLock() 看似方便,但中间有释放读锁的瞬间窗口,其他写线程可能插入并改掉你刚读到的数据。
- 升级不是原子操作:它先释放升级读锁,再争写锁;你基于旧读结果做的判断(如
if (cache == null))可能已失效 - 90% 场景该直接用
EnterWriteLock()开头——写操作本身不重的话,省去升级逻辑反而更安全、更清晰 - 仅当同时满足三个条件才考虑升级:读耗时明显长(如加载大对象)、写极少发生(如配置热更新)、业务允许“读旧值后写失败也接受”(即写锁内必须二次校验关键条件)
Dispose() 必须调,且只能调一次,否则句柄泄漏
ReaderWriterLockSlim 持有内核同步对象句柄,不 Dispose() 会导致句柄持续增长,进程跑久后可能触发 OutOfMemoryException 或系统级句柄耗尽。
- 它不实现
IDisposable的自动释放语义,using块无效;必须显式调用Dispose() - 只能调一次:重复调用会抛
ObjectDisposedException - 锁对象应长期复用(如
static readonly或单例生命周期),绝不能每次读写都new一个——创建开销大,且 Dispose 漏了更难追踪
最常被忽略的一点:它不支持跨 await 持锁。在 async 方法里 EnterReadLock() 后 await,再 ExitReadLock(),必抛 SynchronizationLockException。没有 CancellationToken 支持,也没法“优雅取消等待”。要么改同步路径,要么换 ConcurrentDictionary 这类无锁结构。


















