Когда ReaderWriterLockSlim лучше, чем простой замок?


Я делаю очень глупый тест на ReaderWriterLock с этим кодом, где чтение происходит в 4 раза чаще, чем запись:

class Program
{
    static void Main()
    {
        ISynchro[] test = { new Locked(), new RWLocked() };

        Stopwatch sw = new Stopwatch();

        foreach ( var isynchro in test )
        {
            sw.Reset();
            sw.Start();
            Thread w1 = new Thread( new ParameterizedThreadStart( WriteThread ) );
            w1.Start( isynchro );

            Thread w2 = new Thread( new ParameterizedThreadStart( WriteThread ) );
            w2.Start( isynchro );

            Thread r1 = new Thread( new ParameterizedThreadStart( ReadThread ) );
            r1.Start( isynchro );

            Thread r2 = new Thread( new ParameterizedThreadStart( ReadThread ) );
            r2.Start( isynchro );

            w1.Join();
            w2.Join();
            r1.Join();
            r2.Join();
            sw.Stop();

            Console.WriteLine( isynchro.ToString() + ": " + sw.ElapsedMilliseconds.ToString() + "ms." );
        }

        Console.WriteLine( "End" );
        Console.ReadKey( true );
    }

    static void ReadThread(Object o)
    {
        ISynchro synchro = (ISynchro)o;

        for ( int i = 0; i < 500; i++ )
        {
            Int32? value = synchro.Get( i );
            Thread.Sleep( 50 );
        }
    }

    static void WriteThread( Object o )
    {
        ISynchro synchro = (ISynchro)o;

        for ( int i = 0; i < 125; i++ )
        {
            synchro.Add( i );
            Thread.Sleep( 200 );
        }
    }

}

interface ISynchro
{
    void Add( Int32 value );
    Int32? Get( Int32 index );
}

class Locked:List<Int32>, ISynchro
{
    readonly Object locker = new object();

    #region ISynchro Members

    public new void Add( int value )
    {
        lock ( locker ) 
            base.Add( value );
    }

    public int? Get( int index )
    {
        lock ( locker )
        {
            if ( this.Count <= index )
                return null;
            return this[ index ];
        }
    }

    #endregion
    public override string ToString()
    {
        return "Locked";
    }
}

class RWLocked : List<Int32>, ISynchro
{
    ReaderWriterLockSlim locker = new ReaderWriterLockSlim();

    #region ISynchro Members

    public new void Add( int value )
    {
        try
        {
            locker.EnterWriteLock();
            base.Add( value );
        }
        finally
        {
            locker.ExitWriteLock();
        }
    }

    public int? Get( int index )
    {
        try
        {
            locker.EnterReadLock();
            if ( this.Count <= index )
                return null;
            return this[ index ];
        }
        finally
        {
            locker.ExitReadLock();
        }
    }

    #endregion

    public override string ToString()
    {
        return "RW Locked";
    }
}

но я получаю, что оба выполняют более или менее одинаково:

Locked: 25003ms.
RW Locked: 25002ms.
End

даже читать 20 раз чаще, чем пишет, все (почти) одинаковые.

Я делаю что-то не так здесь?

С уважением.

10 67

10 ответов:

в вашем примере, спит означает, что вообще нет разногласий. Незапертый замок очень быстрый. Чтобы это имело значение, вам понадобится выговор блокировку; если есть пишет в этом утверждении они должны быть примерно одинаковыми (lock может быть даже быстрее) - но если это в основном читает (с претензией на запись редко), я ожидал бы ReaderWriterLockSlim замок, чтобы выполнить lock.

лично я предпочитаю еще одна стратегия здесь, используя справочно-замена - так гласит всегда могу читать без проверки / замок / и т. д. Пишет внести свои изменения в клонировать копировать, затем использовать Interlocked.CompareExchange для замены ссылки (повторное применение их изменения, если другой поток мутировал ссылку в промежуточный период).

мои собственные тесты показывают, что ReaderWriterLockSlim имеет около 5x накладные расходы по сравнению с нормальным lock. Это означает, что для RWLS, чтобы превзойти простой старый замок, как правило, возникают следующие условия.

  • число читателей значительно превышает число писателей.
  • замок должен был бы удерживаться достаточно долго, чтобы преодолеть дополнительные накладные расходы.

в большинстве реальных приложений этих двух условий недостаточно для преодоления это дополнительные накладные расходы. В вашем коде, в частности, блокировки удерживаются в течение такого короткого периода времени, что накладные расходы на блокировку, вероятно, будут доминирующим фактором. Если вы должны были переместить эти Thread.Sleep вызовы внутри замка, то вы, вероятно, получите другой результат.

в этой программе нет никаких разногласий. Методы Get и Add выполняются за несколько наносекунд. Вероятность того, что несколько потоков попадут в эти методы в точное время, исчезающе мала.

поставить какой-нить.Сон(1) вызовите их и удалите сон из потоков, чтобы увидеть разницу.

Edit 2: просто удалив Thread.Sleep звонки ReadThread и WriteThread, увидел Locked превосходит RWLocked. Я верю Ганс хит гвоздь на голове здесь; ваши методы слишком быстры и не создают никаких разногласий. Когда я добавил Thread.Sleep(1) до Get и Add способы Locked и RWLocked (и использовал 4 потока чтения против 1 потока записи),RWLocked бить штаны от Locked.


Edit: хорошо, если бы я был на самом деле мышление когда я впервые написал этот ответ, я бы понял, по крайней мере почему поставить Thread.Sleep вызовы там: вы пытались воспроизвести сценарий чтения происходит чаще, чем пишет. Это просто не правильный способ сделать это. Вместо этого я бы ввел дополнительные накладные расходы для вашего Add и Get методы для создания большего шанса на раздор (как Ганс предложил), создать больше потоков чтения, чем записи потоков (to обеспечьте более частое чтение, чем запись), и удалите Thread.Sleep звонки ReadThread и WriteThread (который на самом деле уменьшить раздор, достижение противоположного тому, что вы хотите).


мне нравится, что вы сделали до сих пор. Но вот несколько вопросов, которые я вижу сходу:

  1. почему Thread.Sleep звонки? Это просто завышение времени выполнения на постоянную величину, что будет искусственно создавать результаты производительности сходиться.
  2. я бы также не включил создание нового Thread объекты в коде, который измеряется ваш Stopwatch. Это не тривиальный объект для создания.

увидите ли вы значительную разницу, как только вы решите эти две проблемы выше, я не знаю. Но я считаю, что они должны быть рассмотрены до того, как обсуждение продолжится.

вы получите лучшую производительность с ReaderWriterLockSlim чем простой замок, Если вы блокируете часть кода, которая требует больше времени для выполнения. В этом случае читатели могут работать параллельно. Приобретение ReaderWriterLockSlim занимает больше времени, чем ввод простой Monitor. Проверьте мой ReaderWriterLockTiny реализация для блокировки readers-writer, которая даже быстрее, чем простой оператор блокировки, и предлагает функциональность readers-writer:http://i255.wordpress.com/2013/10/05/fast-readerwriterlock-for-net/

проверьте эту статью: http://blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx

ваши сны, вероятно, достаточно долго, что они делают вашу блокировку/разблокировку статистически незначимой.

неоспариваемой блокировки занимают порядка микросекунд, чтобы получить, поэтому ваше время выполнения будет затмевать ваши вызовы Sleep.

если у вас нет многоядерного оборудования (или, по крайней мере, такой же, как ваша запланированная производственная среда), Вы не получите реалистичный тест здесь.

более разумным тестом было бы продлить срок службы ваших заблокированных операций, поставив краткую задержку внутри замок. Таким образом, вы действительно сможете сравнить параллелизм, добавленный с помощью ReaderWriterLockSlim по сравнению с сериализацией, подразумеваемой basic lock().

в настоящее время, время ваших операций заблокированные теряются в шуме, создаваемом вызовами сна, которые происходят за пределами блокировок. Общее время в любом случае в основном связано со сном.

вы уверены, что ваше реальное приложение будет иметь равное количество операций чтения и записи? ReaderWriterLockSlim действительно лучше, для случая, когда у вас есть много читателей и относительно редкие писатели. 1 поток записи против 3 потоков чтения должны продемонстрировать ReaderWriterLockSlim преимущества лучше, но в любом случае ваш тест должен соответствовать вашим ожиданиям модель реального доступа.

Я думаю, это из-за спит у вас есть в вас читатель и писатель потоков.
Ваш поток чтения имеет 500tims 50ms сна, который составляет 25000 большую часть времени он спит

когда ReaderWriterLockSlim лучше, чем простой замок?

когда у вас значительно больше читает, чем пишет.