C#中如何实现读写分离
1. 使用 ReaderWriterLockSlim
.NET 提供的高性能读写锁,支持以下模式:
-
读模式(Read Lock):允许多个线程同时读取。
-
写模式(Write Lock):独占锁,其他所有读写操作都会被阻塞。
using System.Threading;
public class ReadWriteExample
{
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private int _sharedData = 0;
// 读操作
public int ReadData()
{
_lock.EnterReadLock();
try
{
return _sharedData;
}
finally
{
_lock.ExitReadLock();
}
}
// 写操作
public void WriteData(int value)
{
_lock.EnterWriteLock();
try
{
_sharedData = value;
}
finally
{
_lock.ExitWriteLock();
}
}
}
优点:
-
细粒度控制读写操作。
-
支持递归锁和超时机制。
缺点:
-
需要手动管理锁的获取和释放。
2. 使用 Concurrent
并发集合
.NET 的 System.Collections.Concurrent
命名空间提供了线程安全的集合类,内部已实现读写分离逻辑:
-
ConcurrentDictionary<TKey, TValue>
-
ConcurrentQueue<T>
-
ConcurrentBag<T>
using System.Collections.Concurrent;
public class ConcurrentExample
{
private readonly ConcurrentDictionary<string, int> _data = new();
// 读操作(无锁)
public int GetValue(string key)
{
return _data.TryGetValue(key, out int value) ? value : -1;
}
// 写操作(内部使用细粒度锁)
public void UpdateValue(string key, int value)
{
_data.AddOrUpdate(key, value, (k, oldValue) => value);
}
}
优点:
-
开箱即用,无需手动管理锁。
-
高性能,适合高频读写场景。
缺点:
-
灵活性较低,仅适用于预定义的集合类型。
3. 基于 volatile
和内存屏障(Memory Barrier)
适用于简单变量的读写分离,通过 volatile
关键字确保内存可见性:
public class VolatileExample
{
private volatile bool _flag = false;
private int _data = 0;
// 读操作(无锁)
public int ReadData()
{
if (_flag)
return _data;
return -1;
}
// 写操作(需要同步)
public void WriteData(int value)
{
Interlocked.Exchange(ref _data, value); // 原子写入
Volatile.Write(ref _flag, true); // 确保写入对其他线程可见
}
}
优点:
-
轻量级,适合简单场景。
-
无锁读操作。
缺点:
-
仅适用于简单类型(如
int
、bool
)。 -
需要手动处理内存可见性。
4. 数据库读写分离
在数据库层面实现读写分离(如主从架构),C# 代码通过不同连接字符串路由请求:
public class DatabaseRouter
{
private static readonly string ReadConnectionString = "Server=ReadServer;...";
private static readonly string WriteConnectionString = "Server=WriteServer;...";
public IDbConnection GetReadConnection() =>
new SqlConnection(ReadConnectionString);
public IDbConnection GetWriteConnection() =>
new SqlConnection(WriteConnectionString);
}
// 使用示例
using (var readConn = DatabaseRouter.GetReadConnection())
{
// 执行查询操作
}
using (var writeConn = DatabaseRouter.GetWriteConnection())
{
// 执行更新操作
}
优点:
-
直接利用数据库主从复制特性。
-
减轻主库压力。
缺点:
-
需要数据库支持主从同步。
-
可能存在数据同步延迟。
5. 基于不可变数据(Immutable Data)
通过每次修改生成新对象实现无锁读取(如使用 ImmutableList<T>
或自定义不可变类型):
using System.Collections.Immutable;
public class ImmutableExample
{
private ImmutableList<int> _data = ImmutableList<int>.Empty;
// 读操作(无锁)
public int GetItem(int index)
{
return _data[index];
}
// 写操作(原子替换)
public void AddItem(int item)
{
ImmutableInterlocked.Update(ref _data, list => list.Add(item));
}
}
优点:
-
完全无锁读取。
-
天然线程安全。
缺点:
-
频繁修改可能产生内存压力。
选择策略
场景 | 推荐方案 |
---|---|
细粒度代码级读写控制 | ReaderWriterLockSlim |
高频并发集合操作 | ConcurrentDictionary 等并发集合 |
简单变量的读写 | volatile + Interlocked |
数据库访问分离 | 主从架构 + 连接路由 |
高频读、低频写 | 不可变数据(如 ImmutableList ) |
注意事项
-
避免死锁:确保锁的获取和释放成对出现(用
try-finally
块)。 -
性能权衡:读写锁适合读多写少场景,写频繁时可能不如普通锁高效。
-
数据一致性:数据库读写分离时需处理主从同步延迟问题。