C# ReaderWriterLockSlim使用方法 C#如何实现高效的读写锁

来源:这里教程网 时间:2026-02-21 17:40:58 作者:

ReaderWriterLockSlim 为什么比 lock 和 Monitor 更适合读多写少场景

因为

ReaderWriterLockSlim
允许多个线程同时读、但写时独占,而
lock
无论读写都串行。在缓存、配置、只读集合等读远多于写的场景下,它能显著提升并发吞吐量。

注意:它不是

ReaderWriterLock
的简单升级版——后者已标记为过时,且内部使用事件内核对象,开销大;
ReaderWriterLockSlim
是用户态实现,轻量,但不支持递归获取(除非显式开启)。

默认不支持同一线程重复进入读锁(
EnterReadLock
调用两次会死锁),需构造时传入
LockRecursionPolicy.SupportsRecursion
不支持跨 await 边界持有锁(即不能在
async
方法中
await
前加锁、await 后解锁),否则会抛出
SynchronizationLockException
写锁优先级高于读锁:一旦有线程调用
EnterWriteLock
,后续的
EnterReadLock
会被阻塞,直到写锁释放

正确初始化和基础读/写模式

声明时推荐使用

new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion)
(默认值),除非你明确需要递归读锁;避免用无参构造函数,防止未来行为变化。

典型用法是配合

try/finally
确保解锁,因为
Dispose()
不会自动释放锁,必须手动调用
ExitReadLock()
ExitWriteLock()

private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
private List<string> _cache = new List<string>();
public string ReadFirst()
{
    _rwLock.EnterReadLock();
    try
    {
        return _cache.FirstOrDefault();
    }
    finally
    {
        _rwLock.ExitReadLock();
    }
}
public void AddItem(string item)
{
    _rwLock.EnterWriteLock();
    try
    {
        _cache.Add(item);
    }
    finally
    {
        _rwLock.ExitWriteLock();
    }
}

如何安全处理超时与取消

EnterReadLock(int millisecondsTimeout)
EnterWriteLock(int millisecondsTimeout)
支持超时,返回
bool
表示是否成功获取锁。超时后不要假设锁已被获取,也不能调用
ExitXxxLock()

没有原生 CancellationToken 支持,但可通过

SpinWait
+ 循环轮询 +
IsCancellationRequested
模拟(不推荐高频轮询)。更实际的做法是:设置合理超时(如 100–500ms),捕获
TimeoutException
后降级或重试。

超时值设为
-1
等价于无限等待(同无参版本)
设为
0
表示“仅尝试一次”,立即返回
false
若锁不可用
不要在高竞争场景下依赖长超时,容易引发请求堆积

常见误用与性能陷阱

最常被忽略的是锁粒度问题:把整个方法体包在

EnterWriteLock
里,却在锁内做了 IO、远程调用或复杂计算,导致其他读写线程长时间阻塞。

另一个隐蔽问题是“读锁中修改共享状态”——看似只读,实则调用了可能改变内部状态的属性或方法(例如

List.Count
安全,但
ObservableCollection.Count
可能触发通知)。

写锁中尽量只做内存操作;耗时逻辑(如文件写入、HTTP 请求)应移出锁外,先计算好结果再进锁更新字段 避免在读锁中调用未审查的第三方方法,尤其涉及事件触发、数据绑定或 LINQ ToObjects 的
ToList()
等可能隐式修改源集合的操作
TryEnterReadLock
TryEnterWriteLock
返回
true
后,必须配对调用对应
ExitXxxLock()
,否则锁泄漏,最终导致所有线程卡死

真正难的不是调用几个方法,而是判断哪段代码该进读锁、哪段该进写锁、以及有没有漏掉边界条件下的状态不一致风险。

相关推荐