多线程六脉神剑第四剑:读写锁 (ReaderWriterLockSlim)
文章目录
- 1、举个栗子
- 2、读写锁的核心本质
- 2.1 状态管理机制
- 2.2 三种锁模式
- 3、底层原理深入解析
- 3.1 锁的兼容性矩阵
- 3.2 原子状态管理
- 3.3 公平性策略
- 4、读写锁的完整使用
- 4.1 基础用法 - 缓存系统
- 4.2 高级用法 - 可升级读锁
- 4.3 复杂协调 - 数据聚合器
- 4.4 超时和尝试获取
- 5、性能优势和适用场景
- 5.1 性能对比测试
- 6、最佳实践和注意事项
- 6.1 正确模式
- 6.2 常见陷阱
- 7、总结
读写锁(ReaderWriterLockSlim)这是处理"读多写少"场景的利器。
1、举个栗子
场景:图书馆的阅览室
-
读者:可以同时进入多人阅读(多个线程并发读取)
-
写者(图书管理员):必须独占整个阅览室来整理书籍(一个线程独占写入)
-
规则:
-
当有读者在阅读时,其他读者可以继续进入
-
当有写者在工作时,所有读者和其他写者都必须等待
-
写者工作时,新的读者也不能进入
-
读写锁的本质:区分读操作和写操作,允许读操作并发执行,但写操作必须独占。
2、读写锁的核心本质
2.1 状态管理机制
读写锁内部维护多个状态计数器:
// 概念性结构 - 展示读写锁的内部状态
class ReaderWriterLockSlimState
{int ReadCount; // 当前活跃的读者数量int WriteCount; // 写者数量(0或1)int UpgradeableReadCount; // 可升级读锁数量(0或1)int WaitingReadCount; // 等待读取的线程数int WaitingWriteCount; // 等待写入的线程数Thread WritingThread; // 当前持有写锁的线程Thread UpgradeableThread; // 当前持有可升级读锁的线程bool WriteLockHeld; // 写锁是否被持有bool UpgradeableLockHeld; // 可升级读锁是否被持有
}
2.2 三种锁模式
class LockModes
{// 1. 读锁(Read Lock)- 共享锁// 多个线程可以同时持有void EnterReadLock();void ExitReadLock();// 2. 写锁(Write Lock)- 独占锁 // 同一时间只有一个线程可以持有void EnterWriteLock();void ExitWriteLock();// 3. 可升级读锁(Upgradeable Read Lock)// 开始是读锁,后面可以升级为写锁void EnterUpgradeableReadLock();void ExitUpgradeableReadLock();
}
3、底层原理深入解析
3.1 锁的兼容性矩阵
读写锁的核心是基于锁的兼容性规则:
| 当前锁状态 | 读锁请求 | 写锁请求 | 可升级读锁请求 |
|---|---|---|---|
| 无锁 | ✅ 允许 | ✅ 允许 | ✅ 允许 |
| 有读锁 | ✅ 允许 | ❌ 拒绝 | ✅ 允许 |
| 有写锁 | ❌ 拒绝 | ❌ 拒绝 | ❌ 拒绝 |
| 有可升级读锁 | ✅ 允许 | ❌ 拒绝 | ❌ 拒绝 |
3.2 原子状态管理
class ReaderWriterLockSlimInternal
{private int _state; // 使用一个32位整数打包多个状态// 状态位的分解(概念性)const int READ_COUNT_BITS = 16;const int WRITE_COUNT_BITS = 8;const int UPGRADE_COUNT_BITS = 8;public bool TryEnterReadLock(){int oldState, newState;do{oldState = _state;// 检查是否可以获取读锁:// 1. 没有写锁被持有// 2. 没有写锁在等待(考虑公平性)// 3. 没有可升级读锁在等待升级if (HasWriteLock(oldState) || (HasWaitingWriters(oldState) && !HasReaders(oldState))){return false; // 不能获取读锁}// 增加读者计数newState = oldState + (1 << READ_COUNT_SHIFT);} while (Interlocked.CompareExchange(ref _state, newState, oldState) != oldState);return true;}public bool TryEnterWriteLock(){int oldState, newState;do{oldState = _state;// 检查是否可以获取写锁:// 1. 没有活跃的读者// 2. 没有写锁被持有// 3. 没有可升级读锁被其他线程持有if (HasReaders(oldState) || HasWriteLock(oldState) || HasUpgradeableLock(oldState)){return false;}// 设置写锁标志newState = oldState | WRITE_LOCK_FLAG;} while (Interlocked.CompareExchange(ref _state, newState, oldState) != oldState);return true;}
}
3.3 公平性策略
为了避免写者饥饿,读写锁采用公平策略:
class FairnessPolicy
{// 当有写者在等待时:public bool ShouldBlockNewReader(){// 如果有写者在等待,并且当前没有活跃读者// 那么新的读者应该等待,让写者先执行return (_waitingWriteCount > 0) && (_activeReadCount == 0);}// 当有读者在等待时:public bool ShouldBlockNewWriter(){// 写者总是要等待所有活跃读者完成return _activeReadCount > 0;}
}
4、读写锁的完整使用
4.1 基础用法 - 缓存系统
class ThreadSafeCache<TKey, TValue>
{private readonly ReaderWriterLockSlim _cacheLock = new ReaderWriterLockSlim();private readonly Dictionary<TKey, TValue> _cache = new Dictionary<TKey, TValue>();private readonly TimeSpan _timeout = TimeSpan.FromSeconds(5);// 读取操作 - 使用读锁(允许多个线程并发读取)public bool TryGetValue(TKey key, out TValue value){_cacheLock.EnterReadLock();try{return _cache.TryGetValue(key, out value);}finally{_cacheLock.ExitReadLock();}}// 写入操作 - 使用写锁(独占访问)public void AddOrUpdate(TKey key, TValue value){if (!_cacheLock.TryEnterWriteLock(_timeout)){throw new TimeoutException("获取写锁超时");}try{_cache[key] = value;Console.WriteLine($"缓存已更新: {key} = {value} (线程: {Thread.CurrentThread.ManagedThreadId})");}finally{_cacheLock.ExitWriteLock();}}// 批量读取 - 多个线程可以同时执行public Dictionary<TKey, TValue> GetAllValues(){_cacheLock.EnterReadLock();try{// 模拟耗时读取操作Thread.Sleep(100);return new Dictionary<TKey, TValue>(_cache);}finally{_cacheLock.ExitReadLock();}}public int Count{get{_cacheLock.EnterReadLock();try{return _cache.Count;}finally{_cacheLock.ExitReadLock();}}}
}// 使用示例
class CacheExample
{static void Main(){var cache = new ThreadSafeCache<string, string>();// 启动多个读取线程var readers = new List<Thread>();for (int i = 0; i < 5; i++){int readerId = i;readers.Add(new Thread(() => ReaderWork(cache, readerId)));}// 启动写入线程var writer = new Thread(() => WriterWork(cache));// 启动所有线程readers.ForEach(t => t.Start());writer.Start();// 等待完成readers.ForEach(t => t.Join());writer.Join();Console.WriteLine($"最终缓存项数: {cache.Count}");}static void ReaderWork(ThreadSafeCache<string, string> cache, int readerId){for (int i = 0; i < 10; i++){string key = $"key_{i % 3}";if (cache.TryGetValue(key, out string value)){Console.WriteLine($"读者{readerId} 读取: {key} = {value}");}Thread.Sleep(50);}}static void WriterWork(ThreadSafeCache<string, string> cache){for (int i = 0; i < 5; i++){cache.AddOrUpdate($"key_{i}", $"value_{DateTime.Now:HH:mm:ss.fff}");Thread.Sleep(200);}}
}
4.2 高级用法 - 可升级读锁
这是读写锁最强大的特性,用于"读后写"场景:
class UpgradeableCache<TKey, TValue> where TValue : new()
{private readonly ReaderWriterLockSlim _cacheLock = new ReaderWriterLockSlim();private readonly Dictionary<TKey, TValue> _cache = new Dictionary<TKey, TValue>();// 获取或添加模式 - 使用可升级读锁public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory){// 第一步:获取可升级读锁(允许多个读者,但只能有一个可升级读者)_cacheLock.EnterUpgradeableReadLock();try{// 第二步:检查缓存中是否已存在TValue value;if (_cache.TryGetValue(key, out value)){Console.WriteLine($"缓存命中: {key} (线程: {Thread.CurrentThread.ManagedThreadId})");return value;}// 第三步:不存在,升级为写锁_cacheLock.EnterWriteLock();try{// 双重检查,防止其他线程已经添加if (!_cache.TryGetValue(key, out value)){Console.WriteLine($"缓存未命中,创建: {key} (线程: {Thread.CurrentThread.ManagedThreadId})");value = valueFactory(key);_cache[key] = value;}return value;}finally{_cacheLock.ExitWriteLock();}}finally{_cacheLock.ExitUpgradeableReadLock();}}// 懒惰初始化模式public TValue GetOrCreate(TKey key) where TValue : new(){return GetOrAdd(key, k => new TValue());}
}// 使用示例
class UpgradeableExample
{static void Main(){var cache = new UpgradeableCache<string, List<int>>();Parallel.For(0, 20, i => {var list = cache.GetOrCreate("shared_list");lock (list){list.Add(i);Console.WriteLine($"线程 {Thread.CurrentThread.ManagedThreadId} 添加 {i}, 列表大小: {list.Count}");}});var finalList = cache.GetOrCreate("shared_list");Console.WriteLine($"最终列表大小: {finalList.Count}");}
}
4.3 复杂协调 - 数据聚合器
class DataAggregator
{private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();private readonly Dictionary<string, double> _metrics = new Dictionary<string, double>();private readonly List<string> _log = new List<string>();private DateTime _lastResetTime = DateTime.Now;// 高频更新 - 使用写锁public void UpdateMetric(string name, double value){_rwLock.EnterWriteLock();try{_metrics[name] = value;_log.Add($"{DateTime.Now:HH:mm:ss.fff} 更新 {name} = {value}");// 限制日志大小if (_log.Count > 1000){_log.RemoveRange(0, _log.Count - 100);}}finally{_rwLock.ExitWriteLock();}}// 低频读取 - 使用读锁(多个监控线程可以并发读取)public MetricsSnapshot GetSnapshot(){_rwLock.EnterReadLock();try{return new MetricsSnapshot{Metrics = new Dictionary<string, double>(_metrics),LastResetTime = _lastResetTime,Timestamp = DateTime.Now};}finally{_rwLock.ExitReadLock();}}// 报告生成 - 使用可升级读锁public Report GenerateReport(){_rwLock.EnterUpgradeableReadLock();try{var snapshot = new MetricsSnapshot{Metrics = new Dictionary<string, double>(_metrics),LastResetTime = _lastResetTime,Timestamp = DateTime.Now};// 如果报告需要重置数据,升级为写锁if (snapshot.Timestamp - _lastResetTime > TimeSpan.FromMinutes(5)){_rwLock.EnterWriteLock();try{// 重置过期数据_lastResetTime = DateTime.Now;_metrics.Clear();_log.Clear();Console.WriteLine("数据已重置");}finally{_rwLock.ExitWriteLock();}}return new Report { Snapshot = snapshot, LogEntries = _log.Take(10).ToList() };}finally{_rwLock.ExitUpgradeableReadLock();}}public class MetricsSnapshot{public Dictionary<string, double> Metrics { get; set; }public DateTime LastResetTime { get; set; }public DateTime Timestamp { get; set; }}public class Report{public MetricsSnapshot Snapshot { get; set; }public List<string> LogEntries { get; set; }}
}
4.4 超时和尝试获取
class TimeoutExample
{private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();private readonly Dictionary<string, string> _data = new Dictionary<string, string>();public bool TryReadWithTimeout(string key, out string value, int timeoutMs = 5000){if (_rwLock.TryEnterReadLock(timeoutMs)){try{return _data.TryGetValue(key, out value);}finally{_rwLock.ExitReadLock();}}else{value = null;Console.WriteLine($"获取读锁超时 ({timeoutMs}ms)");return false;}}public bool TryWriteWithTimeout(string key, string value, int timeoutMs = 5000){if (_rwLock.TryEnterWriteLock(timeoutMs)){try{_data[key] = value;return true;}finally{_rwLock.ExitWriteLock();}}else{Console.WriteLine($"获取写锁超时 ({timeoutMs}ms)");return false;}}public bool TryUpgradeableReadWithTimeout(string key, Func<string, string> factory, int timeoutMs = 5000){if (_rwLock.TryEnterUpgradeableReadLock(timeoutMs)){try{string existingValue;if (_data.TryGetValue(key, out existingValue)){return true;}// 尝试升级if (_rwLock.TryEnterWriteLock(timeoutMs)){try{// 双重检查if (!_data.ContainsKey(key)){_data[key] = factory(key);}return true;}finally{_rwLock.ExitWriteLock();}}else{Console.WriteLine($"升级写锁超时 ({timeoutMs}ms)");return false;}}finally{_rwLock.ExitUpgradeableReadLock();}}else{Console.WriteLine($"获取可升级读锁超时 ({timeoutMs}ms)");return false;}}
}
5、性能优势和适用场景
5.1 性能对比测试
class PerformanceBenchmark
{private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();private readonly object _monitorLock = new object();private int _sharedData = 0;public void TestReaderWriterLock(int readerCount, int writerCount){var stopwatch = Stopwatch.StartNew();var readers = new List<Thread>();var writers = new List<Thread>();// 创建读者线程for (int i = 0; i < readerCount; i++){readers.Add(new Thread(() => {for (int j = 0; j < 1000; j++){_rwLock.EnterReadLock();try{int value = _sharedData; // 读取Thread.SpinWait(100); // 模拟读取工作}finally{_rwLock.ExitReadLock();}}}));}// 创建写者线程for (int i = 0; i < writerCount; i++){writers.Add(new Thread(() => {for (int j = 0; j < 100; j++){_rwLock.EnterWriteLock();try{_sharedData++; // 写入Thread.SpinWait(500); // 模拟写入工作}finally{_rwLock.ExitWriteLock();}}}));}// 启动所有线程readers.ForEach(t => t.Start());writers.ForEach(t => t.Start());// 等待完成readers.ForEach(t => t.Join());writers.ForEach(t => t.Join());Console.WriteLine($"读写锁: {readerCount}读者/{writerCount}写者 -> {stopwatch.ElapsedMilliseconds}ms");}public void TestMonitorLock(int readerCount, int writerCount){var stopwatch = Stopwatch.StartNew();var readers = new List<Thread>();var writers = new List<Thread>();// 创建读者线程for (int i = 0; i < readerCount; i++){readers.Add(new Thread(() => {for (int j = 0; j < 1000; j++){lock (_monitorLock){int value = _sharedData; // 读取Thread.SpinWait(100); // 模拟读取工作}}}));}// 创建写者线程for (int i = 0; i < writerCount; i++){writers.Add(new Thread(() => {for (int j = 0; j < 100; j++){lock (_monitorLock){_sharedData++; // 写入Thread.SpinWait(500); // 模拟写入工作}}}));}// 启动所有线程readers.ForEach(t => t.Start());writers.ForEach(t => t.Start());// 等待完成readers.ForEach(t => t.Join());writers.ForEach(t => t.Join());Console.WriteLine($"Monitor锁: {readerCount}读者/{writerCount}写者 -> {stopwatch.ElapsedMilliseconds}ms");}
}
6、最佳实践和注意事项
6.1 正确模式
class BestPractices
{private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();public void GoodPattern1(){// 总是使用 try-finally_rwLock.EnterReadLock();try{// 读取操作}finally{_rwLock.ExitReadLock();}}public void GoodPattern2(){// 可升级读锁的正确用法_rwLock.EnterUpgradeableReadLock();try{// 检查条件if (NeedWriteOperation()){_rwLock.EnterWriteLock();try{// 写入操作}finally{_rwLock.ExitWriteLock();}}}finally{_rwLock.ExitUpgradeableReadLock();}}public void GoodPattern3(){// 使用超时避免死锁if (_rwLock.TryEnterWriteLock(5000)){try{// 写入操作}finally{_rwLock.ExitWriteLock();}}else{// 超时处理}}
}
6.2 常见陷阱
class CommonMistakes
{private ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();// 错误1:忘记释放锁public void Mistake1(){_rwLock.EnterReadLock();// 如果这里发生异常,锁永远不会释放!// 应该使用 try-finally_rwLock.ExitReadLock();}// 错误2:在可升级读锁外尝试升级public void Mistake2(){_rwLock.EnterReadLock();try{// 不能直接从读锁升级!_rwLock.EnterWriteLock(); // 抛出 LockRecursionException}finally{_rwLock.ExitReadLock();}}// 错误3:递归死锁public void Mistake3(){_rwLock.EnterReadLock();try{// 同一个线程内,读锁中不能再获取读锁AnotherReadMethod(); // 如果里面也获取读锁,会死锁}finally{_rwLock.ExitReadLock();}}// 错误4:错误的锁顺序public void Mistake4(){Thread t1 = new Thread(() => {_rwLock.EnterReadLock();Thread.Sleep(1000);_rwLock.EnterWriteLock(); // 死锁!});Thread t2 = new Thread(() => {Thread.Sleep(100);_rwLock.EnterWriteLock(); // 被t1的读锁阻塞});}
}
7、总结
读写锁的本质:
-
是区分读写操作的智能锁
-
通过状态机管理锁的兼容性
-
使用原子操作维护读者计数和写者状态
-
采用公平策略避免写者饥饿
核心优势:
-
读操作并发:多个线程可以同时读取,极大提升读密集型应用性能
-
写操作安全:保证写操作的原子性和一致性
-
灵活的升级:支持从读锁安全升级到写锁
-
避免锁竞争:读操作之间不会相互阻塞
适用场景:
-
读多写少的数据结构(缓存、配置、元数据)
-
高频读取、低频更新的业务场景
-
需要生成快照的监控和统计系统
-
延迟初始化模式
使用要点:
-
读多写少的场景使用性能最佳
-
总是使用 try-finally 确保锁释放
-
合理使用可升级读锁避免死锁
-
考虑使用超时控制避免长时间阻塞
