2014-01-23 50 views
1

我正在写一个读写同步类,并希望对接下来我要做的事提出一些建议。出于某种原因,它有时会允许Read发生在Write的中间,我无法找到原因。读写同步类的实现

这是我从这个类要:

  • 读出未同时允许作为写道。
  • 读取倍数可以同时发生。
  • 一次只能写入一个。
  • 当需要写入时,所有正在执行的读取操作都会继续, 当所有读取操作完成后,不会执行新的读取操作。

我知道.Net框架有一个类来做到这一点......但我想要的是理解和重现这样的东西。我不是在重新发明轮子,我试图通过制造我自己的轮子来理解它......发生的事情是我的轮子有点平坦。

什么我现在是这样的:

public class ReadWriteSync 
{ 
    private ManualResetEvent read = new ManualResetEvent(true); 
    private volatile int readingBlocks = 0; 
    private AutoResetEvent write = new AutoResetEvent(true); 
    private object locker = new object(); 

    public IDisposable ReadLock() 
    { 
     lock (this.locker) 
     { 
      this.write.Reset(); 
      Interlocked.Increment(ref this.readingBlocks); 
      this.read.WaitOne(); 
     } 

     return new Disposer(() => 
     { 
      if (Interlocked.Decrement(ref this.readingBlocks) == 0) 
       this.write.Set(); 
     }); 
    } 

    public IDisposable WriteLock() 
    { 
     lock (this.locker) 
     { 
      this.read.Reset(); 
      this.write.WaitOne(); 
     } 

     return new Disposer(() => 
     { 
      this.read.Set(); 
      if (this.readingBlocks == 0) 
       this.write.Set(); 
     }); 
    } 

    class Disposer : IDisposable 
    { 
     Action disposer; 
     public Disposer(Action disposer) { this.disposer = disposer; } 
     public void Dispose() { this.disposer(); } 
    } 
} 

这是我的测试程序......出问题的时候它打印的线条为红色。

class Program 
{ 
    static ReadWriteSync sync = new ReadWriteSync(); 

    static void Main(string[] args) 
    { 
     Console.BackgroundColor = ConsoleColor.DarkGray; 
     Console.ForegroundColor = ConsoleColor.Gray; 
     Console.Clear(); 

     Task readTask1 = new Task(() => DoReads("A", 20)); 
     Task readTask2 = new Task(() => DoReads("B", 30)); 
     Task readTask3 = new Task(() => DoReads("C", 40)); 
     Task readTask4 = new Task(() => DoReads("D", 50)); 

     Task writeTask1 = new Task(() => DoWrites("E", 500)); 
     Task writeTask2 = new Task(() => DoWrites("F", 200)); 

     readTask1.Start(); 
     readTask2.Start(); 
     readTask3.Start(); 
     readTask4.Start(); 

     writeTask1.Start(); 
     writeTask2.Start(); 

     Task.WaitAll(
      readTask1, readTask2, readTask3, readTask4, 
      writeTask1, writeTask2); 
    } 

    static volatile bool reading; 
    static volatile bool writing; 

    static void DoWrites(string name, int interval) 
    { 
     for (int i = 1; i < int.MaxValue; i += 2) 
     { 
      using (sync.WriteLock()) 
      { 
       Console.ForegroundColor = (writing || reading) ? ConsoleColor.Red : ConsoleColor.Gray; 
       writing = true; 
       Console.WriteLine("WRITE {1}-{0} BEGIN", i, name); 
       Thread.Sleep(interval); 
       Console.WriteLine("WRITE {1}-{0} END", i, name); 
       writing = false; 
      } 

      Thread.Sleep(interval); 
     } 
    } 

    static void DoReads(string name, int interval) 
    { 
     for (int i = 0; i < int.MaxValue; i += 2) 
     { 
      using (sync.ReadLock()) 
      { 
       Console.ForegroundColor = (writing) ? ConsoleColor.Red : ConsoleColor.Gray; 
       reading = true; 
       Console.WriteLine("READ {1}-{0} BEGIN", i, name); 
       Thread.Sleep(interval * 3); 
       Console.WriteLine("READ {1}-{0} END", i, name); 
       reading = false; 
      } 

      Thread.Sleep(interval); 
     } 
    } 
} 

所有这些有什么问题......关于如何正确使用它的任何建议?

回答

2

我看到的主要问题是,您正试图使重置事件包含读/写的含义以及处理其当前状态,而没有以一致的方式进行同步。

下面是一个示例,说明不一致的同步如何在特定的代码中引起您的注意。

  • write被设置和一read在到来。
  • read获取锁
  • write设置read ManualResetEvent的(MRE)
  • write检查当前读取计数,发现0
  • read重置write AutoResetEvent(ARE)
  • read递增读取计数
  • read认定其MRE已定,开始读

一切都很好,到目前为止,但write尚未完成...

  • 第二write进来,获取锁
  • 第二write通过设定复位read MRE
  • 第一write结束该write ARE
  • 第二write认定其都有已经设置并开始写

在考虑多个线程时,除非您处于某种类型的锁定之内,否则您必须认为所有其他数据都非常波动并且不可信。

一个天真的实现可能会从状态逻辑中分出排队逻辑并进行适当的同步。

public class ReadWrite 
    { 
     private static int readerCount = 0; 
     private static int writerCount = 0; 
     private int pendingReaderCount = 0; 
     private int pendingWriterCount = 0; 
     private readonly object decision = new object(); 

     private class WakeLock:IDisposable 
     { 
      private readonly object wakeLock; 
      public WakeLock(object wakeLock) { this.wakeLock = wakeLock; } 
      public virtual void Dispose() { lock(this.wakeLock) Monitor.PulseAll(this.wakeLock); } 
     } 
     private class ReadLock:WakeLock 
     { 
      public ReadLock(object wakeLock) : base(wakeLock) { Interlocked.Increment(ref readerCount); } 
      public override void Dispose() 
      { 
       Interlocked.Decrement(ref readerCount); 
       base.Dispose(); 
      } 
     }    
     private class WriteLock:WakeLock 
     { 
      public WriteLock(object wakeLock) : base(wakeLock) { Interlocked.Increment(ref writerCount); } 
      public override void Dispose() 
      { 
       Interlocked.Decrement(ref writerCount); 
       base.Dispose(); 
      } 
     } 

     public IDisposable TakeReadLock() 
     { 
      lock(decision) 
      { 
       pendingReaderCount++; 
       while (pendingWriterCount > 0 || Thread.VolatileRead(ref writerCount) > 0) 
        Monitor.Wait(decision); 
       pendingReaderCount--; 
       return new ReadLock(this.decision); 
      } 
     } 

     public IDisposable TakeWriteLock() 
     { 
      lock(decision) 
      { 
       pendingWriterCount++; 
       while (Thread.VolatileRead(ref readerCount) > 0 || Thread.VolatileRead(ref writerCount) > 0) 
        Monitor.Wait(decision); 
       pendingWriterCount--; 
       return new WriteLock(this.decision); 
      } 
     } 
    }