Когда 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 ответов:
в вашем примере, спит означает, что вообще нет разногласий. Незапертый замок очень быстрый. Чтобы это имело значение, вам понадобится выговор блокировку; если есть пишет в этом утверждении они должны быть примерно одинаковыми (
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(который на самом деле уменьшить раздор, достижение противоположного тому, что вы хотите).
мне нравится, что вы сделали до сих пор. Но вот несколько вопросов, которые я вижу сходу:
- почему
Thread.Sleepзвонки? Это просто завышение времени выполнения на постоянную величину, что будет искусственно создавать результаты производительности сходиться.- я бы также не включил создание нового
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по сравнению с сериализацией, подразумеваемой basiclock().в настоящее время, время ваших операций заблокированные теряются в шуме, создаваемом вызовами сна, которые происходят за пределами блокировок. Общее время в любом случае в основном связано со сном.
вы уверены, что ваше реальное приложение будет иметь равное количество операций чтения и записи?
ReaderWriterLockSlimдействительно лучше, для случая, когда у вас есть много читателей и относительно редкие писатели. 1 поток записи против 3 потоков чтения должны продемонстрироватьReaderWriterLockSlimпреимущества лучше, но в любом случае ваш тест должен соответствовать вашим ожиданиям модель реального доступа.