问题描述
读者写者也是一个非常著名的同步问题,也叫共享独占锁。问题描述非常简单,有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者在读文件时写者也不去能写文件。因此要求:①允许多个读者可以同时对文件执行读操作;②同时只允许一个写者往文件中写信息;③任一写者在完成写操作之前不允许其他读者或写者工作;④写者执行写操作前,应让已有的读者和写者全部退出。
问题分析
写者和读者不仅是互斥而且是同步的,为此可以用一个事件或者信号量来实现,
这样实现了写者与读者对资源的同步访问,但是这个情况只允许一个写者或者一个读者使用资源,对于写者来说,这是没有问题的,但是无法实现多个读者并发访问资源。
只有一个reader能使用资源。
这说明以上解决方案还有问题,那么实际上应该是如果一旦有读者开始读文件,那么写者就应该等待,而此时更多的读者也可以同时读取;而当最后一个读者离开时,此时写者就可以写文件。根据以上分析,我们可以使用一个计数器来判断当前是否有读者读文件。
对于计数器,因为并发多个读者访问,为保证正确结果,需要再需要一个信号量来同步计数过程,如下:
根据这个可以写出代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using ThreadUtilities;
namespace ReaderWriter
{
class Program
{
static void Main(string[] args)
{
ReaderWriterProblem rwp = new ReaderWriterProblem(3);
rwp.Start();
rwp.Stop(300);
//ReaderWriterProblemEx rwp = new ReaderWriterProblemEx(3);
//rwp.Start();
//rwp.Stop(300);
}
/// <summary>
/// two thread, one is writer thread,the other is reader thread,
/// because the reader can't read when the writer is writing,the writer shouldn't write until there is not reader:
/// SO:
/// The writer thread is synchronized with the reader thread
/// Reader threads can be concurrent when there is not writers
///
/// </summary>
class ReaderWriterProblem
{
AutoResetEvent mWRSyncEvent;
AutoResetEvent mReaderCountEvent;
CustomResetEvent mWaitHandle;
int mCurReaderCount = 0;
int ConcurrencyReaderCount = 1;
int mWriteTimes = 0;
bool bStop = false;
public ReaderWriterProblem(int readercount)
{
ConcurrencyReaderCount = readercount;
mWaitHandle = new CustomResetEvent(ConcurrencyReaderCount+1);
}
public void Start()
{
mWRSyncEvent = new AutoResetEvent(true);
mReaderCountEvent = new AutoResetEvent(true);
Thread writertd = new Thread(Write);
writertd.Start();
for (int i = 0; i < ConcurrencyReaderCount; i++)
{
Thread readertd = new Thread(Read);
readertd.Start(i);
}
}
public void Stop(int durationforrunning)
{
Thread.Sleep(durationforrunning);
bStop = true;
mWaitHandle.Wait();
}
void Write()
{
while (!bStop)
{
mWRSyncEvent.WaitOne();
//do writing
Console.WriteLine(" {0} The file is be writing... " + (mWriteTimes++),DateTime.Now.ToString("hh:mm:ss fff"));
Thread.Sleep(200);
// signal that writing is finished
Console.WriteLine("Writer exit!");
mWRSyncEvent.Set();
}
mWaitHandle.SignalFinishOne();
}
void Read(object readid)
{
while (!bStop)
{
mReaderCountEvent.WaitOne();
mCurReaderCount++;
if (mCurReaderCount == 1)
{
mWRSyncEvent.WaitOne();//siganl the file is not able to be written
}
Console.WriteLine("reader " + readid + " login...");
Console.WriteLine("The current reader count is " + mCurReaderCount);
mReaderCountEvent.Set();
// do reading
Console.WriteLine("{0} reader " + readid + " is reading...", DateTime.Now.ToString("hh:mm:ss fff"));
Thread.Sleep(100);
//exit
mReaderCountEvent.WaitOne();
mCurReaderCount--;
if (mCurReaderCount == 0)
{
mWRSyncEvent.Set();
}
Console.WriteLine("reader " + readid + " exit!");
Console.WriteLine("The current reader count is " + mCurReaderCount);
mReaderCountEvent.Set();
}
mWaitHandle.SignalFinishOne();
}
}
}
}
运行效果:
ReaderWriterLockSlim类提供了可升级读模式,这种方式和读模式的区别在于它还有通过调用EnterWriteLock 或TryEnterWriteLock 方法升级为写入模式。具体例子可以参考msdn
https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim(v=vs.110).aspx
但是通过看《CLR via C#》对该类的解析可以得知,这其实也是一种很耗资源的低效行为。
在此,我们只是通过它来简化实现我们上面的过程。使用该类来实现读者写者问题可以屏蔽了同步及计数器问题。
代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using ThreadUtilities;
namespace ReaderWriter
{
class Program
{
static void Main(string[] args)
{
//ReaderWriterProblem rwp = new ReaderWriterProblem(3);
//rwp.Start();
//rwp.Stop(300);
ReaderWriterProblemEx rwp = new ReaderWriterProblemEx(3);
rwp.Start();
rwp.Stop(300);
}
class ReaderWriterProblemEx
{
ReaderWriterLockSlim mReaderWirterLock;
CustomResetEvent mWaitHandle;
int ConcurrencyReaderCount = 1;
int mWriteTimes = 0;
bool bStop = false;
public ReaderWriterProblemEx(int readercount)
{
ConcurrencyReaderCount = readercount;
mWaitHandle = new CustomResetEvent(ConcurrencyReaderCount + 1);
}
~ReaderWriterProblemEx()
{
if (mReaderWirterLock != null) mReaderWirterLock.Dispose();
}
public void Start()
{
mReaderWirterLock = new ReaderWriterLockSlim();
Thread writertd = new Thread(Writer);
writertd.Start();
for (int i = 0; i < ConcurrencyReaderCount; i++)
{
Thread readertd = new Thread(Reader);
readertd.Start(i);
}
}
public void Stop(int durationforrunning)
{
Thread.Sleep(durationforrunning);
bStop = true;
mWaitHandle.Wait();
}
void Writer()
{
while (!bStop)
{
mReaderWirterLock.EnterWriteLock();
Console.WriteLine(" {0} The file is be writing... " + (mWriteTimes++), DateTime.Now.ToString("hh:mm:ss fff"));
Thread.Sleep(200);
Console.WriteLine("Writer exit!");
mReaderWirterLock.ExitWriteLock();
}
mWaitHandle.SignalFinishOne();
}
void Reader(object readid)
{
while (!bStop)
{
Console.WriteLine("{0} reader " + readid + " try to login...", DateTime.Now.ToString("hh:mm:ss fff"));
mReaderWirterLock.EnterReadLock();
Console.WriteLine("{0} reader " + readid + " login...", DateTime.Now.ToString("hh:mm:ss fff"));
// do reading
Thread.Sleep(100);
Console.WriteLine("{0} reader " + readid + " try to exit", DateTime.Now.ToString("hh:mm:ss fff"));
mReaderWirterLock.ExitReadLock();
Console.WriteLine("reader " + readid + " exit!");
}
mWaitHandle.SignalFinishOne();
}
}
}
}
运行效果:
存在问题:
以上的实现都是读进程是优先的,也就是说,当存在读进程时,写操作将被延迟,并且只要有一个读进程活跃,随后而来的读进程都将被允许访问文件。这样的方式下,会导致写进程可能长时间等待,且存在写进程“饿死”的情况。
具体解决可以参考
https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem
http://c.biancheng.net/cpp/html/2601.html
另外参考:
http://blog.csdn.net/morewindows/article/details/7596034
https://www.youtube.com/watch?v=_vfZhdTgA5A3:56