总目录
前言
ReaderWriterLockSlim
是 C# 中用于实现读写锁的高效线程同步工具,位于 System.Threading 命名空间下。它提供了一种轻量级的机制来实现读写锁(Read-Write Lock),允许多个线程同时读取共享资源,但在写入时独占访问。适用于读多写少的场景(如缓存、配置管理)。相较于旧版 ReaderWriterLock
,它在性能和设计上更优。
一、核心概念
- 读写锁机制:
- 读模式(Read Lock):允许多个线程同时读取资源,提高并发性能。
- 写模式(Write Lock):
- 独占资源,阻塞所有读/写操作。
- 当有线程请求写入时,阻止其他线程读取或写入,确保数据一致性。
- 可升级读模式(Upgradeable Read Lock):
- 允许线程在持有读锁的情况下升级为写锁。
- 支持从读锁升级为写锁,并且可以从写锁降级为读锁。
- 适用场景:
- 高并发读低并发写:高频读取、低频写入的共享资源(如缓存、配置)。
- 避免阻塞读操作:允许多个线程同时读取共享资源,而不会相互阻塞。
- 需要细粒度控制读写操作的并发性。
二、基本用法
1. 初始化锁
var rwLock = new ReaderWriterLockSlim();
2. 主要方法和属性
方法 | 作用 |
---|---|
EnterReadLock() /ExitReadLock() | 进入读模式(允许多线程并行读取)/ 释放读锁。 |
EnterWriteLock() / ExitWriteLock() | 进入写模式(独占资源)/释放写锁。 |
EnterUpgradeableReadLock() | 进入可升级读模式(允许后续升级为写锁)。 |
ExitUpgradeableReadLock() | 释放可升级读锁。 |
CurrentReadCount | 获取当前持有读锁的线程数。 |
IsReadLockHeld | 检查当前线程是否持有读锁。 |
IsWriteLockHeld | 检查当前线程是否持有写锁。 |
IsUpgradeableReadLockHeld | 检查当前线程是否持有可升级读锁。 |
Dispose() | 释放锁资源(继承自 IDisposable )。 |
三、示例
示例 1:基础读写操作
using System.Threading;
class Program
{
static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
static Dictionary<int, string> cache = new Dictionary<int, string>();
static void Main()
{
// 启动多个读线程和写线程
new Thread(WriteData).Start(1);
new Thread(WriteData).Start(2);
new Thread(ReadData).Start(1);
new Thread(ReadData).Start(2);
new Thread(ReadData).Start(3);
Console.ReadKey();
}
static void ReadData(object key)
{
rwLock.EnterReadLock();
try
{
if (cache.TryGetValue((int)key, out var value))
{
Console.WriteLine($"读取键 {key}: {value}");
}
else
{
Console.WriteLine($"读取键 {key} 失败");
}
}
finally
{
rwLock.ExitReadLock();
}
}
static void WriteData(object key)
{
rwLock.EnterWriteLock();
try
{
cache[(int)key] = $"New Value {new Random().Next()}";
Console.WriteLine($"写入键 {key}:{cache[(int)key]}");
}
finally
{
rwLock.ExitWriteLock();
}
}
}
输出
写入键 1:New Value 1748854959
写入键 2:New Value 1022241692
读取键 1: New Value 1748854959
读取键 2: New Value 1022241692
读取键 3 失败
示例 2:基础读写操作
using System;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
static string sharedResource = "Initial Value";
static async Task Main(string[] args)
{
// 启动多个读线程
for (int i = 0; i < 5; i++)
{
int j = i + 1;
Task.Run(() => ReadTask($"Reader {j}"));
}
// 启动一个写线程
Task.Run(() => WriteTask("Writer"));
// 等待所有任务完成
await Task.Delay(5000);
Console.WriteLine("所有任务已完成");
}
static void ReadTask(string name)
{
while (true)
{
try
{
rwLock.EnterReadLock();
Console.WriteLine($"{name} 正在读取: {sharedResource}");
Thread.Sleep(100); // 模拟读取时间
}
finally
{
rwLock.ExitReadLock();
}
// 随机决定是否继续读取
if (new Random().NextDouble() > 0.8)
{
break;
}
}
}
static void WriteTask(string name)
{
while (true)
{
try
{
rwLock.EnterWriteLock();
Console.WriteLine($"{name} 正在写入...");
sharedResource = $"New Value at {DateTime.Now}";
Console.WriteLine($"{name} 写入完成: {sharedResource}");
Thread.Sleep(2000); // 模拟写入时间
}
finally
{
rwLock.ExitWriteLock();
}
// 随机决定是否继续写入
if (new Random().NextDouble() > 0.8)
{
break;
}
}
}
}
Reader 2 正在读取: Initial Value
Reader 1 正在读取: Initial Value
Reader 3 正在读取: Initial Value
Reader 4 正在读取: Initial Value
Reader 5 正在读取: Initial Value
Writer 正在写入...
Writer 写入完成: New Value at 2025/2/13 11:23:10
Reader 2 正在读取: New Value at 2025/2/13 11:23:10
Reader 5 正在读取: New Value at 2025/2/13 11:23:10
Reader 3 正在读取: New Value at 2025/2/13 11:23:10
Writer 正在写入...
Writer 写入完成: New Value at 2025/2/13 11:23:12
Reader 3 正在读取: New Value at 2025/2/13 11:23:12
Reader 1 正在读取: New Value at 2025/2/13 11:23:12
Reader 2 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
Reader 2 正在读取: New Value at 2025/2/13 11:23:12
Reader 3 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
Reader 2 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
Reader 4 正在读取: New Value at 2025/2/13 11:23:12
所有任务已完成
四、可升级读锁(Upgradeable Read Lock)
适用场景
- 需要先读取数据,再根据条件决定是否修改(例如缓存填充)。
- 避免直接使用写锁导致不必要的阻塞。
有时我们需要先读取资源,然后根据读取的结果决定是否需要进行写操作。这时可以使用可升级读锁(Upgradeable Read Lock)。以下是使用可升级读锁的示例:
示例
static void SafeWriteWithUpgrade(int key)
{
rwLock.EnterUpgradeableReadLock();
try
{
if (!cache.ContainsKey(key))
{
rwLock.EnterWriteLock();
try
{
cache[key] = "Initialized Value";
Console.WriteLine($"初始化键 {key}");
}
finally
{
rwLock.ExitWriteLock();
}
}
}
finally
{
rwLock.ExitUpgradeableReadLock();
}
}
static void UpgradeableReadTask(string name)
{
while (true)
{
try
{
rwLock.EnterUpgradeableReadLock();
Console.WriteLine($"{name} 正在读取: {sharedResource}");
// 根据某些条件决定是否需要写入
if (new Random().NextDouble() > 0.8)
{
try
{
rwLock.EnterWriteLock();
Console.WriteLine($"{name} 正在升级到写锁并写入...");
sharedResource = $"Upgraded Value at {DateTime.Now}";
Console.WriteLine($"{name} 写入完成: {sharedResource}");
Thread.Sleep(2000); // 模拟写入时间
}
finally
{
rwLock.ExitWriteLock();
}
}
Thread.Sleep(100); // 模拟读取时间
}
finally
{
rwLock.ExitUpgradeableReadLock();
}
// 随机决定是否继续读取
if (new Random().NextDouble() > 0.8)
{
break;
}
}
}
五、注意事项
1. 锁的释放
- 必须使用
try-finally
确保锁被释放,否则会导致死锁。 - 在实际应用中,建议在 EnterReadLock()、EnterWriteLock() 和 EnterUpgradeableReadLock() 调用周围添加 try-finally 块,以确保即使在发生异常的情况下也能正确地释放锁。
- 错误示例:
rwLock.EnterReadLock(); // 若此处抛出异常,锁未释放! rwLock.ExitReadLock();
2. 递归调用
- 默认情况下,
ReaderWriterLockSlim
不支持递归调用。 - 若需递归,需使用
LockRecursionPolicy.SupportsRecursion
初始化:var rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
3. 性能优化
- 避免长时间持有写锁,以减少阻塞。
- 可升级读锁仅在必要时使用,因其性能开销较高。
六、高级用法
1. 超时等待
bool acquired = rwLock.TryEnterWriteLock(TimeSpan.FromSeconds(2));
if (acquired)
{
try
{
// 执行写入操作
}
finally
{
rwLock.ExitWriteLock();
}
}
else
{
Console.WriteLine("获取写锁超时");
}
2. 结合异步编程
async Task UpdateCacheAsync(int key)
{
await Task.Run(() =>
{
rwLock.EnterWriteLock();
try
{
cache[key] = await FetchDataFromNetworkAsync();
}
finally
{
rwLock.ExitWriteLock();
}
});
}
七、替代方案
ConcurrentDictionary
:内置线程安全的字典,适合简单读写场景。Immutable Collections
:不可变集合,天然线程安全。Monitor
或Mutex
:适合简单独占访问,但无法区分读写。
总结
ReaderWriterLockSlim
是实现高效读写分离同步的核心工具,尤其适用于读多写少的场景。通过合理使用读锁、写锁和可升级读锁,可以显著提升并发性能。但需注意锁的释放、递归策略及死锁风险。在高并发场景中,建议结合性能测试选择最优方案。
结语
回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
xxx