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