读者-写者问题(Readers-Writers Problem)
问题描述:
在多线程环境中,有多个线程尝试同时访问共享资源。有两种线程:
- 读者(Readers): 只需要读取资源,不会修改它。
- 写者(Writers): 修改共享资源,因此需要独占访问权限。
目标是:
- 允许多个读者同时读取资源(提高效率)。
- 如果写者正在访问资源,则读者和其他写者必须等待(保证一致性)。
常见解决方案的优先级策略
-
读者优先: 如果有读者在等待且没有写者,则允许读者优先访问。
- 优点:提高读者的吞吐量。
- 缺点:可能导致写者饥饿。
-
写者优先: 如果有写者在等待,则写者优先访问。
- 优点:确保写者不会饥饿。
- 缺点:可能导致读者延迟过长。
-
公平(无饥饿): 保证读者和写者都能公平地访问资源。
C# 解决方案
1. 使用 ReaderWriterLockSlim
C# 提供了 ReaderWriterLockSlim
,它是解决读者-写者问题的一个高效工具,支持:
- 读锁(ReadLock): 允许多个线程同时读取资源。
- 写锁(WriteLock): 仅允许一个线程写入资源,同时阻止其他读者和写者。
代码示例:
using System;
using System.Threading;
using System.Threading.Tasks;
class ReaderWriterExample
{
private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
private static int sharedResource = 0;
static void Main(string[] args)
{
// 启动多个读者线程
for (int i = 0; i < 5; i++)
{
int readerId = i;
Task.Run(() => Reader(readerId));
}
// 启动多个写者线程
for (int i = 0; i < 2; i++)
{
int writerId = i;
Task.Run(() => Writer(writerId));
}
Console.ReadLine();
}
static void Reader(int readerId)
{
while (true)
{
rwLock.EnterReadLock();
try
{
Console.WriteLine($"Reader {readerId} reads: {sharedResource}");
}
finally
{
rwLock.ExitReadLock();
}
Thread.Sleep(500); // 模拟读取时间
}
}
static void Writer(int writerId)
{
while (true)
{
rwLock.EnterWriteLock();
try
{
sharedResource++;
Console.WriteLine($"Writer {writerId} updates resource to: {sharedResource}");
}
finally
{
rwLock.ExitWriteLock();
}
Thread.Sleep(1000); // 模拟写入时间
}
}
}
2. 手动实现读者-写者锁
如果 ReaderWriterLockSlim
不满足需求,可以手动实现读者优先或写者优先的逻辑。
示例:写者优先实现
using System;
using System.Threading;
class ManualReaderWriter
{
private static int readers = 0; // 当前读者数量
private static bool writerActive = false; // 是否有写者正在操作
private static object lockObj = new object();
static void Reader(int readerId)
{
while (true)
{
lock (lockObj)
{
while (writerActive) Monitor.Wait(lockObj); // 等待写者完成
readers++;
}
Console.WriteLine($"Reader {readerId} is reading");
Thread.Sleep(500); // 模拟读取操作
lock (lockObj)
{
readers--;
if (readers == 0) Monitor.PulseAll(lockObj); // 唤醒等待的写者
}
}
}
static void Writer(int writerId)
{
while (true)
{
lock (lockObj)
{
while (readers > 0 || writerActive) Monitor.Wait(lockObj); // 等待读者或写者完成
writerActive = true;
}
Console.WriteLine($"Writer {writerId} is writing");
Thread.Sleep(1000); // 模拟写入操作
lock (lockObj)
{
writerActive = false;
Monitor.PulseAll(lockObj); // 唤醒等待的线程
}
}
}
static void Main(string[] args)
{
for (int i = 0; i < 3; i++) new Thread(() => Reader(i)).Start();
for (int i = 0; i < 2; i++) new Thread(() => Writer(i)).Start();
Console.ReadLine();
}
}
总结
- 读者优先策略: 提高读取性能,但可能造成写者饥饿。
- 写者优先策略: 避免写者饥饿,但可能延迟读者访问。
- 公平策略: 平衡读写的访问顺序,避免饥饿。
- 工具: 使用 C# 提供的
ReaderWriterLockSlim
是推荐的解决方案,除非需要特殊的自定义逻辑。