1

私は読み書き同期クラスを書いていますが、次に何をすべきかアドバイスが欲しいです。何らかの理由でRead、 の途中で が発生するWriteことがありますが、その理由がわかりません。

これは私がこのクラスに望むものです:

  • 書き込みと同時に読み取りを行うことはできません。
  • 複数の読み取りが同時に発生する可能性があります。
  • 一度に実行できる書き込みは 1 つだけです。
  • 書き込みが必要な場合、既に実行中のすべての読み取りが続行され、新しい読み取りは許可されず、すべての読み取りが終了すると書き込みが実行されます。

.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);
        }
    }
}

このすべての何が問題なのですか...正しく行う方法について何かアドバイスはありますか?

4

1 に答える 1

3

私が見る主な問題は、一貫した方法で同期することなく、リセットイベントに読み取り/書き込みの意味と現在の状態の処理の両方を含めようとしているということです。

これは、一貫性のない同期が特定のコードでどのように噛み付くかの例です。

  • Awriteが処分し、 A がread入ってきます。
  • readがロックを取得します
  • ManualResetEvent (MRE)writeを設定します。read
  • 現在のwrite読み取り回数をチェックし、0 を見つけます。
  • AutoResetEvent (ARE)readをリセットします。write
  • read読み取りカウントをインクリメントします
  • は、そのreadMRE が設定されていることを検出し、読み取りを開始します。

今のところ大丈夫ですが、writeまだ終わっていません...

  • writeが入り、ロックを取得します
  • 2番目はMREwriteをリセットしますread
  • 最初はAREwriteを設定して終了しますwrite
  • 2番目writeは、AREが設定されていることを発見し、書き込みを開始します

複数のスレッドについて考える場合、ある種のロック内にない限り、他のすべてのデータは大きく変動し、信頼できないという見方をする必要があります。

これの単純な実装では、状態ロジックからキューイング ロジックを分割し、適切に同期することができます。

    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);
            }
        }
    }
于 2014-01-27T21:23:35.933 に答える