必须用 ReaderWriterLockSlim 替代已过时且被移除的 ReaderWriterLock;它轻量、可重入、支持超时与取消,但需严格配对 Enter/Exit 并避免局部变量声明。

别用 ReaderWriterLock —— 它已过时、线程不安全、且在 .NET Core/.NET 5+ 中被彻底移除。 真正该用的是 ReaderWriterLockSlim,它才是现代 C# 中唯一推荐的读写锁实现。
为什么 ReaderWriterLock 必须淘汰
这个类在 .NET Framework 1.1 就存在,但设计有严重缺陷:
-
AcquireWriterLock和AcquireReaderLock可能引发死锁(比如嵌套调用或超时后未释放) - 不支持异步等待,阻塞线程无法取消
- 内部使用
Monitor+ 复杂状态机,性能差、调试困难 - .NET Core 1.0 起就不再实现该类,
dotnet build会直接报错:类型未找到
ReaderWriterLockSlim 的正确初始化与基本用法
它轻量、可重入(默认)、支持超时和取消,是唯一可选方案。注意:构造函数参数控制行为,不是“越复杂越好”。
- 默认构造:
new ReaderWriterLockSlim()→ 允许递归获取锁(同一线程可多次 EnterReadLock),适合多数场景 - 禁用递归:
new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion)→ 避免意外嵌套,但抛出LockRecursionException时需捕获 - 指定超时策略(不常用):
new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion, 1000)→ 所有Enter*方法默认最多等 1 秒
典型读/写模式:
var rwLock = new ReaderWriterLockSlim();
// 读操作
rwLock.EnterReadLock();
try {
// 访问共享只读数据,如 List<string> _cache;
} finally {
rwLock.ExitReadLock();
}
// 写操作
rwLock.EnterWriteLock();
try {
// 修改共享数据,如 _cache.Add(item);
} finally {
rwLock.ExitWriteLock();
}</string>
必须加 try/finally,不能依赖 using
ReaderWriterLockSlim 不实现 IDisposable,所以不能用 using 自动释放。漏掉 Exit* 会导致锁永远不释放,整个应用卡死。
- 写操作中抛异常?没
finally→ 锁永久占用 → 后续所有读写全阻塞 - 读操作嵌套调用?递归模式下
EnterReadLock次数必须等于ExitReadLock次数,否则计数错乱 - 更安全的写法:用
TryEnterReadLock(int millisecondsTimeout)或TryEnterWriteLock,返回bool判断是否成功获取
性能与替代方案:什么情况下不该用它
读写锁不是银弹。高并发读+低频写时表现好;但若写操作频繁,或临界区极小(比如只改一个 int),它反而比 lock 慢 2–5 倍。
- 纯数值更新:优先用
Interlocked系列(Interlocked.Increment、Interlocked.CompareExchange) - 集合操作:考虑
ConcurrentDictionary、ConcurrentQueue,它们无锁且线程安全 - 需要取消等待?用
TryEnterWriteLock(Timeout.Infinite, cancellationToken)(.NET 6+) - 跨进程?它只作用于当前进程内线程,别和
Mutex混淆
真正容易被忽略的一点:锁对象生命周期必须长于所有访问它的线程。把 ReaderWriterLockSlim 声明为局部变量,或者随 Entity 被 GC 回收,会导致锁失效且无提示。











