09、读写锁
读写锁
知识点
什么是读写锁
读写锁(Reader-Writer Lock)是一种特殊的同步机制,允许多个线程同时读取共享资源,但写入时需要独占访问。这种机制在读多写少的场景下能显著提高性能。
ReaderWriterLockSlim类
.NET提供了ReaderWriterLockSlim类来实现读写锁功能,它是ReaderWriterLock的改进版本,性能更好,功能更强。
读写锁的工作原理
- 读锁(共享锁):多个线程可以同时持有读锁
- 写锁(独占锁):只有一个线程可以持有写锁
- 升级锁:从读锁升级为写锁的特殊机制
锁的类型
- 读锁(Read Lock):允许并发读取
- 写锁(Write Lock):独占写入
- 可升级读锁(Upgradeable Read Lock):可以升级为写锁的读锁
使用场景
- 缓存系统
- 配置管理
- 数据字典
- 读多写少的数据结构
代码案例
案例1:基本的读写锁使用
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
class BasicReaderWriterLock
{
private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
private static Dictionary<string, string> cache = new Dictionary<string, string>();
static void Main(string[] args)
{
Console.WriteLine("基本读写锁示例");
// 初始化一些数据
InitializeCache();
// 启动多个读线程
Task[] readers = new Task[5];
for (int i = 0; i < readers.Length; i++)
{
int readerId = i;
readers[i] = Task.Run(() => ReaderTask(readerId));
}
// 启动少量写线程
Task[] writers = new Task[2];
for (int i = 0; i < writers.Length; i++)
{
int writerId = i;
writers[i] = Task.Run(() => WriterTask(writerId));
}
// 等待所有任务完成
Task.WaitAll(readers.Concat(writers).ToArray());
Console.WriteLine("所有任务完成");
DisplayCache();
// 清理资源
rwLock.Dispose();
}
static void InitializeCache()
{
rwLock.EnterWriteLock();
try
{
cache["key1"] = "value1";
cache["key2"] = "value2";
cache["key3"] = "value3";
Console.WriteLine("缓存初始化完成");
}
finally
{
rwLock.ExitWriteLock();
}
}
static void ReaderTask(int readerId)
{
for (int i = 0; i < 10; i++)
{
rwLock.EnterReadLock();
try
{
Console.WriteLine($"读线程{readerId}: 开始读取缓存");
// 模拟读取操作
foreach (var kvp in cache)
{
Console.WriteLine($"读线程{readerId}: {kvp.Key} = {kvp.Value}");
}
Thread.Sleep(100); // 模拟读取时间
Console.WriteLine($"读线程{readerId}: 读取完成");
}
finally
{
rwLock.ExitReadLock();
}
Thread.Sleep(200); // 等待一段时间再次读取
}
}
static void WriterTask(int writerId)
{
for (int i = 0; i < 5; i++)
{
rwLock.EnterWriteLock();
try
{
Console.WriteLine($"写线程{writerId}: 开始写入缓存");
string key = $"writer{writerId}_key{i}";
string value = $"writer{writerId}_value{i}_{DateTime.Now:HH:mm:ss.fff}";
cache[key] = value;
Console.WriteLine($"写线程{writerId}: 写入 {key} = {value}");
Thread.Sleep(500); // 模拟写入时间
Console.WriteLine($"写线程{writerId}: 写入完成");
}
finally
{
rwLock.ExitWriteLock();
}
Thread.Sleep(1000); // 等待一段时间再次写入
}
}
static void DisplayCache()
{
rwLock.EnterReadLock();
try
{
Console.WriteLine("\n=== 最终缓存内容 ===");
foreach (var kvp in cache)
{
Console.WriteLine($"{kvp.Key} = {kvp.Value}");
}
}
finally
{
rwLock.ExitReadLock();
}
}
}
案例2:可升级读锁的使用
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
class UpgradeableReadLockExample
{
private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
private static Dictionary<string, CacheItem> cache = new Dictionary<string, CacheItem>();
class CacheItem
{
public string Value { get; set; }
public DateTime LastAccessed { get; set; }
public int AccessCount { get; set; }
}
static void Main(string[] args)
{
Console.WriteLine("可升级读锁示例");
// 初始化缓存
InitializeCache();
// 启动多个任务
Task[] tasks = new Task[8];
// 读任务
for (int i = 0; i < 4; i++)
{
int taskId = i;
tasks[i] = Task.Run(() => ReadWithStatistics(taskId));
}
// 可升级读任务
for (int i = 4; i < 6; i++)
{
int taskId = i;
tasks[i] = Task.Run(() => UpgradeableReadTask(taskId));
}
// 纯写任务
for (int i = 6; i < 8; i++)
{
int taskId = i;
tasks[i] = Task.Run(() => WriteTask(taskId));
}
Task.WaitAll(tasks);
Console.WriteLine("所有任务完成");
DisplayCacheStatistics();
rwLock.Dispose();
}
static void InitializeCache()
{
rwLock.EnterWriteLock();
try
{
for (int i = 1; i <= 5; i++)
{
cache[$"item{i}"] = new CacheItem
{
Value = $"value{i}",
LastAccessed = DateTime.Now,
AccessCount = 0
};
}
Console.WriteLine("缓存初始化完成");
}
finally
{
rwLock.ExitWriteLock();
}
}
static void ReadWithStatistics(int taskId)
{
for (int i = 0; i < 10; i++)
{
string key = $"item{(i % 5) + 1}";
// 使用可升级读锁来读取并更新统计信息
rwLock.EnterUpgradeableReadLock();
try
{
Console.WriteLine($"任务{taskId}: 尝试读取 {key}");
if (cache.ContainsKey(key))
{
var item = cache[key];
Console.WriteLine($"任务{taskId}: 读取到 {key} = {item.Value}");
// 需要更新统计信息,升级为写锁
rwLock.EnterWriteLock();
try
{
item.LastAccessed = DateTime.Now;
item.AccessCount++;
Console.WriteLine($"任务{taskId}: 更新 {key} 的统计信息,访问次数: {item.AccessCount}");
}
finally
{
rwLock.ExitWriteLock();
}
}
else
{
Console.WriteLine($"任务{taskId}: {key} 不存在");
}
}
finally
{
rwLock.ExitUpgradeableReadLock();
}
Thread.Sleep(300);
}
}
static void UpgradeableReadTask(int taskId)
{
for (int i = 0; i < 5; i++)
{
string key = $"upgrade_item{i}";
rwLock.EnterUpgradeableReadLock();
try
{
Console.WriteLine($"升级任务{taskId}: 检查 {key} 是否存在");
if (!cache.ContainsKey(key))
{
// 如果不存在,升级为写锁并创建
rwLock.EnterWriteLock();
try
{
Console.WriteLine($"升级任务{taskId}: {key} 不存在,创建新项");
cache[key] = new CacheItem
{
Value = $"upgraded_value{taskId}_{i}",
LastAccessed = DateTime.Now,
AccessCount = 1
};
Console.WriteLine($"升级任务{taskId}: 创建 {key} 完成");
}
finally
{
rwLock.ExitWriteLock();
}
}
else
{
Console.WriteLine($"升级任务{taskId}: {key} 已存在,值为 {cache[key].Value}");
}
}
finally
{
rwLock.ExitUpgradeableReadLock();
}
Thread.Sleep(800);
}
}
static void WriteTask(int taskId)
{
for (int i = 0; i < 3; i++)
{
rwLock.EnterWriteLock();
try
{
string key = $"write_item{taskId}_{i}";
Console.WriteLine($"写任务{taskId}: 写入 {key}");
cache[key] = new CacheItem
{
Value = $"write_value{taskId}_{i}_{DateTime.Now:HH:mm:ss.fff}",
LastAccessed = DateTime.Now,
AccessCount = 0
};
Thread.Sleep(200); // 模拟写入时间
Console.WriteLine($"写任务{taskId}: {key} 写入完成");
}
finally
{
rwLock.ExitWriteLock();
}
Thread.Sleep(1500);
}
}
static void DisplayCacheStatistics()
{
rwLock.EnterReadLock();
try
{
Console.WriteLine("\n=== 缓存统计信息 ===");
foreach (var kvp in cache)
{
var item = kvp.Value;
Console.WriteLine($"{kvp.Key}: 值={item.Value}, 访问次数={item.AccessCount}, 最后访问={item.LastAccessed:HH:mm:ss.fff}");
}
}
finally
{
rwLock.ExitReadLock();
}
}
}
案例3:超时和异常处理
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
class ReaderWriterLockTimeout
{
private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
private static Dictionary<string, string> dataStore = new Dictionary<string, string>();
static void Main(string[] args)
{
Console.WriteLine("读写锁超时处理示例");
// 启动一个长时间持有写锁的任务
Task longWriteTask = Task.Run(() => LongWriteOperation());
Thread.Sleep(100); // 确保写任务先获得锁
// 启动多个尝试获取锁的任务
Task[] tasks = new Task[6];
for (int i = 0; i < 3; i++)
{
int taskId = i;
tasks[i] = Task.Run(() => TryReadWithTimeout(taskId));
}
for (int i = 3; i < 6; i++)
{
int taskId = i;
tasks[i] = Task.Run(() => TryWriteWithTimeout(taskId));
}
Task.WaitAll(new[] { longWriteTask }.Concat(tasks).ToArray());
Console.WriteLine("所有任务完成");
rwLock.Dispose();
}
static void LongWriteOperation()
{
Console.WriteLine("长写操作: 开始获取写锁...");
rwLock.EnterWriteLock();
try
{
Console.WriteLine("长写操作: 获得写锁,开始长时间操作...");
for (int i = 0; i < 10; i++)
{
dataStore[$"long_key_{i}"] = $"long_value_{i}";
Thread.Sleep(500); // 模拟长时间写入
Console.WriteLine($"长写操作: 写入进度 {i + 1}/10");
}
Console.WriteLine("长写操作: 完成");
}
finally
{
rwLock.ExitWriteLock();
Console.WriteLine("长写操作: 释放写锁");
}
}
static void TryReadWithTimeout(int taskId)
{
for (int attempt = 0; attempt < 3; attempt++)
{
Console.WriteLine($"读任务{taskId}: 尝试获取读锁(尝试 {attempt + 1}/3)...");
try
{
if (rwLock.TryEnterReadLock(TimeSpan.FromSeconds(2)))
{
try
{
Console.WriteLine($"读任务{taskId}: 成功获得读锁");
// 执行读操作
int count = dataStore.Count;
Console.WriteLine($"读任务{taskId}: 当前数据项数量: {count}");
Thread.Sleep(300); // 模拟读取时间
}
finally
{
rwLock.ExitReadLock();
Console.WriteLine($"读任务{taskId}: 释放读锁");
}
break; // 成功后退出重试循环
}
else
{
Console.WriteLine($"读任务{taskId}: 获取读锁超时,执行替代方案");
// 执行不需要锁的替代操作
Console.WriteLine($"读任务{taskId}: 从缓存或其他源获取数据");
Thread.Sleep(100);
}
}
catch (Exception ex)
{
Console.WriteLine($"读任务{taskId}: 异常 - {ex.Message}");
}
Thread.Sleep(1000); // 等待后重试
}
}
static void TryWriteWithTimeout(int taskId)
{
for (int attempt = 0; attempt < 2; attempt++)
{
Console.WriteLine($"写任务{taskId}: 尝试获取写锁(尝试 {attempt + 1}/2)...");
try
{
if (rwLock.TryEnterWriteLock(TimeSpan.FromSeconds(3)))
{
try
{
Console.WriteLine($"写任务{taskId}: 成功获得写锁");
// 执行写操作
string key = $"task{taskId}_attempt{attempt}";
string value = $"value_{DateTime.Now:HH:mm:ss.fff}";
dataStore[key] = value;
Console.WriteLine($"写任务{taskId}: 写入 {key} = {value}");
Thread.Sleep(200); // 模拟写入时间
}
finally
{
rwLock.ExitWriteLock();
Console.WriteLine($"写任务{taskId}: 释放写锁");
}
break; // 成功后退出重试循环
}
else
{
Console.WriteLine($"写任务{taskId}: 获取写锁超时");
// 可以选择将写操作加入队列或记录日志
Console.WriteLine($"写任务{taskId}: 将写操作加入待处理队列");
}
}
catch (Exception ex)
{
Console.WriteLine($"写任务{taskId}: 异常 - {ex.Message}");
}
Thread.Sleep(1500); // 等待后重试
}
}
}
案例4:读写锁性能对比
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
class ReaderWriterLockPerformance
{
private static Dictionary<int, string> data = new Dictionary<int, string>();
private static readonly object simpleLock = new object();
private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
static void Main(string[] args)
{
Console.WriteLine("读写锁性能对比示例");
// 初始化数据
InitializeData();
int readTasks = 10;
int writeTasks = 2;
int operationsPerTask = 1000;
Console.WriteLine($"测试参数: {readTasks}个读任务, {writeTasks}个写任务, 每个任务{operationsPerTask}次操作");
// 测试简单锁性能
TimeSpan simpleLockTime = TestSimpleLock(readTasks, writeTasks, operationsPerTask);
// 重置数据
InitializeData();
// 测试读写锁性能
TimeSpan rwLockTime = TestReaderWriterLock(readTasks, writeTasks, operationsPerTask);
// 输出对比结果
Console.WriteLine("\n=== 性能对比结果 ===");
Console.WriteLine($"简单锁耗时: {simpleLockTime.TotalMilliseconds:F2} ms");
Console.WriteLine($"读写锁耗时: {rwLockTime.TotalMilliseconds:F2} ms");
Console.WriteLine($"性能提升: {(simpleLockTime.TotalMilliseconds / rwLockTime.TotalMilliseconds):F2}x");
rwLock.Dispose();
}
static void InitializeData()
{
data.Clear();
for (int i = 0; i < 1000; i++)
{
data[i] = $"value_{i}";
}
}
static TimeSpan TestSimpleLock(int readTasks, int writeTasks, int operationsPerTask)
{
Console.WriteLine("\n--- 测试简单锁 ---");
var stopwatch = Stopwatch.StartNew();
Task[] allTasks = new Task[readTasks + writeTasks];
// 创建读任务
for (int i = 0; i < readTasks; i++)
{
int taskId = i;
allTasks[i] = Task.Run(() => SimpleLockReadTask(taskId, operationsPerTask));
}
// 创建写任务
for (int i = 0; i < writeTasks; i++)
{
int taskId = i;
allTasks[readTasks + i] = Task.Run(() => SimpleLockWriteTask(taskId, operationsPerTask));
}
Task.WaitAll(allTasks);
stopwatch.Stop();
Console.WriteLine($"简单锁测试完成,耗时: {stopwatch.ElapsedMilliseconds} ms");
return stopwatch.Elapsed;
}
static TimeSpan TestReaderWriterLock(int readTasks, int writeTasks, int operationsPerTask)
{
Console.WriteLine("\n--- 测试读写锁 ---");
var stopwatch = Stopwatch.StartNew();
Task[] allTasks = new Task[readTasks + writeTasks];
// 创建读任务
for (int i = 0; i < readTasks; i++)
{
int taskId = i;
allTasks[i] = Task.Run(() => ReaderWriterLockReadTask(taskId, operationsPerTask));
}
// 创建写任务
for (int i = 0; i < writeTasks; i++)
{
int taskId = i;
allTasks[readTasks + i] = Task.Run(() => ReaderWriterLockWriteTask(taskId, operationsPerTask));
}
Task.WaitAll(allTasks);
stopwatch.Stop();
Console.WriteLine($"读写锁测试完成,耗时: {stopwatch.ElapsedMilliseconds} ms");
return stopwatch.Elapsed;
}
static void SimpleLockReadTask(int taskId, int operations)
{
for (int i = 0; i < operations; i++)
{
lock (simpleLock)
{
int key = i % 1000;
string value = data.ContainsKey(key) ? data[key] : null;
// 模拟读取处理
}
}
}
static void SimpleLockWriteTask(int taskId, int operations)
{
for (int i = 0; i < operations; i++)
{
lock (simpleLock)
{
int key = (taskId * 10000) + i;
data[key] = $"task{taskId}_value_{i}";
}
}
}
static void ReaderWriterLockReadTask(int taskId, int operations)
{
for (int i = 0; i < operations; i++)
{
rwLock.EnterReadLock();
try
{
int key = i % 1000;
string value = data.ContainsKey(key) ? data[key] : null;
// 模拟读取处理
}
finally
{
rwLock.ExitReadLock();
}
}
}
static void ReaderWriterLockWriteTask(int taskId, int operations)
{
for (int i = 0; i < operations; i++)
{
rwLock.EnterWriteLock();
try
{
int key = (taskId * 10000) + i;
data[key] = $"task{taskId}_value_{i}";
}
finally
{
rwLock.ExitWriteLock();
}
}
}
}
知识点总结
-
读写锁的优势:
- 允许多个读者并发访问
- 写操作时独占访问
- 在读多写少场景下性能显著提升
-
锁的类型和使用:
- 读锁:
EnterReadLock()/ExitReadLock() - 写锁:
EnterWriteLock()/ExitWriteLock() - 可升级读锁:
EnterUpgradeableReadLock()/ExitUpgradeableReadLock()
- 读锁:
-
最佳实践:
- 总是使用try-finally确保锁的释放
- 使用TryEnter方法设置超时
- 合理使用可升级读锁避免死锁
- 保持锁的粒度适当
-
性能考虑:
- 读写锁适用于读多写少的场景
- 写操作频繁时性能可能不如简单锁
- 可升级读锁有额外开销
-
注意事项:
- 避免锁升级时的死锁
- 正确处理异常以防止锁泄漏
- 使用using语句或Dispose清理资源

浙公网安备 33010602011771号