Когда 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 раз чаще, что пишет, производительность по-прежнему (почти) одинакова.

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

С уважением.

Ответ 1

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

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

Ответ 2

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

Поместите вызов Thread.Sleep(1) в них и удалите сон из потоков, чтобы увидеть разницу.

Ответ 3

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

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

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

Ответ 4

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


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


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

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

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

Ответ 5

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

Ответ 7

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

Ответ 8

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

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

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

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

Ответ 9

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

Ответ 10

Когда ReaderWriterLockSlim лучше, чем простая блокировка?

Если у вас значительно больше чтений, чем записи.