商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > C#中ReaderWriterLockSlim的用法_C#读写锁优化并发读取教程【高级】

C#中ReaderWriterLockSlim的用法_C#读写锁优化并发读取教程【高级】

  发布于2026-07-03 阅读(0)

扫一扫,手机访问

先说几个核心判断:ReaderWriterLockSlim 绝不是 lock 的平替版本,它真正的用武之地是“读多写少”场景下的吞吐量优化。用错了地方,它非但不会更快,反而比 lock 更容易让你掉进死锁的坑里。

先说说最容易被忽视的一个陷阱:为什么你不能对一个线程连续调用两次 EnterReadLock?

默认构造的 ReaderWriterLockSlim,采用的递归策略是 LockRecursionPolicy.NoRecursion。这意味着锁的内部机制不允许同一个线程再次进入读取模式。这不是一个提示,而是硬性阻塞——第二次调用 EnterReadLock() 会无休止地等下去,因为锁不会给这个线程再计一次数。

很多团队踩过这个坑:在嵌套方法或者递归调用里,读锁被无意中多次触发,结果线程挂在那里,没有任何日志,也没有异常抛出来,排查起来非常头疼。

所以,千万不要用无参构造函数 new ReaderWriterLockSlim()。显式地传入策略参数,会让整个逻辑更可控。如果确实需要递归读(虽然极少见),初始化时必须指定 LockRecursionPolicy.SupportsRecursion。但代价也很清楚:逻辑复杂度上升,死锁风险也随之增加。

更安全的做法是:把锁的粒度收窄,尽量在单个方法内完成加锁和解锁,避免跨方法持有锁。或者干脆用局部变量加上 try/finally,确保每次调用都成对出现。

另一个常见的坑:TryEnterWriteLock 的超时返回 false 后,你还能调用 ExitWriteLock 吗?

答案是不能。TryEnterWriteLock(int) 返回 false,意味着线程根本没有进入写入模式。此时调用 ExitWriteLock(),会直接抛出 SynchronizationLockException

一个典型的现场:在 if (!rwLock.TryEnterWriteLock(100)) { ... } 的分支外,无条件地执行了 ExitWriteLock()。正确的结构必须严格配对,只有在 TryEnterWriteLock 返回 true 的分支内,才用 try/finally 包裹。

另外,超时值设置为 -1 等价于无限等待,和无参的 EnterWriteLock() 行为一致。生产环境建议设定在 100–500ms 之间,避免写操作长时间阻塞读请求。

还有一个需要注意的点:这套机制没有原生的 CancellationToken 支持。别想着在 await 前加锁、await 后解锁,那会直接触发 SynchronizationLockException

第三个故事:可升级读锁(UpgradeableReadLock)真的是“读+写”的快捷方式吗?

EnterUpgradeableReadLock() 允许你后续调用 EnterWriteLock() 而不释放读权限。但这里有个关键限制:它仍然受写锁优先级制约。一旦其他线程已经调用了 EnterWriteLock(),所有升级尝试都会被阻塞,包括你自己的升级路径。

这个特性的适用场景其实很窄,基本只适合“先查再改、且读写操作紧密耦合”的情况,比如“若 key 不存在则插入”。

升级前必须确认当前线程没有持有普通读锁,否则会因递归策略冲突抛出 LockRecursionException。而且,升级失败时不会自动回退到读状态,你需要手动处理降级逻辑,比如捕获超时后重新走一遍纯读流程。

说句更直白的:滥用可升级锁,往往只是掩盖了设计上的问题。真正该做的,是重构数据访问模式,而不是靠锁机制来打补丁。

最后说说 Dispose:你以为 using 语句能帮你释放锁吗?

ReaderWriterLockSlim 实现了 IDisposable,但 Dispose() 只清理内部资源,不会释放任何已持有的读锁或写锁。如果你在 using 块里忘了调用 ExitXxxLock(),其他线程会永久阻塞。

所以必须坚持一个底线原则:EnterExit 成对出现。try/finally 是保护伞,using 在这里帮不上忙。

锁对象本身应该作为类的 readonly 字段长期存活,而不是按需创建。频繁地 newDispose,只会把性能优势抵消得一干二净。

还有一点容易被忽略:只有加锁的线程才能解锁。跨线程调用 ExitReadLock(),会立即抛异常。最终的释放时机应该由业务生命周期决定,比如服务停用时调用 Dispose(),而不是每次操作后都来一次。

说到底,最容易被人忽略的一点是:锁解决的是并发安全,而线程安全的数据结构本身不是它的目标。用 ReaderWriterLockSlim 包裹一个非线程安全的 List,完全合理。但用它去包裹一个 ConcurrentDictionary,就有点画蛇添足了——后者内部已经做了细粒度的分段锁,再套一层读写锁,只会徒增开销,毫无收益。

本文转载于:https://www.php.cn/faq/2458175.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注