Я провожу очень глупый тест на 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 раз чаще, чем писать, производительность остается (почти) такой же.
Я что-то здесь делаю не так?
С уважением.
c#
.net
multithreading
locking
втортола
источник
источник
Ответы:
В вашем примере сны означают, что обычно нет разногласий. Неконтролируемая блокировка происходит очень быстро. Для этого вам понадобится конкурирующая блокировка; если есть запись в этом утверждении, они должны быть примерно такой же (
lock
может быть даже быстрее) - но если он в основном читает (с записью раздора редко), я бы ожидать , чтоReaderWriterLockSlim
замок на вне выполнятьlock
.Лично я предпочитаю здесь другую стратегию, использующую обмен ссылками - поэтому операции чтения всегда можно читать без проверки / блокировки и т. Д. Записи вносят изменения в клонированную копию, а затем используют
Interlocked.CompareExchange
для замены ссылки (повторное применение их изменений, если другой поток видоизменил ссылку тем временем).источник
bool bDone=false; while(!bDone) { object origObj = theObj; object newObj = origObj.DeepCopy(); // then make changes to newObj if(Interlocked.CompareExchange(ref theObj, tempObj, newObj)==origObj) bDone=true; }
Interlocked
и просто использоватьvolatile
поле и просто назначить ему - но если вам нужно быть уверенным, что ваша сдача не потерялась полностью, тоInterlocked
это идеальныйМои собственные тесты показывают, что
ReaderWriterLockSlim
накладные расходы примерно в 5 раз выше, чем у обычногоlock
. Это означает, что RWLS превосходит простую старую блокировку, как правило, возникают следующие условия.В большинстве реальных приложений этих двух условий недостаточно для преодоления этих дополнительных накладных расходов. В частности, в вашем коде блокировки удерживаются на такой короткий период времени, что накладные расходы на блокировку, вероятно, будут доминирующим фактором. Если бы вы переместили эти
Thread.Sleep
вызовы внутрь блокировки, вы, вероятно, получили бы другой результат.источник
В этой программе нет разногласий. Методы Get и Add выполняются за несколько наносекунд. Вероятность того, что несколько потоков задействуют эти методы в точное время, исчезающе мала.
Поместите в них вызов Thread.Sleep (1) и удалите сон из потоков, чтобы увидеть разницу.
источник
Изменить 2 : просто удалить
Thread.Sleep
вызовы изReadThread
иWriteThread
, я увидел, чтоLocked
превзошелRWLocked
. Я считаю, что здесь Ганс попал в самую точку; ваши методы слишком быстрые и не вызывают разногласий. Когда я добавилThread.Sleep(1)
к методамGet
andAdd
методыLocked
andRWLocked
(и использовал 4 потока чтения против 1 потока записи), я неRWLocked
выдержалLocked
.Изменить : Хорошо, если бы я действительно думал, когда впервые опубликовал этот ответ, я бы понял, по крайней мере, почему вы помещаете
Thread.Sleep
туда вызовы: вы пытались воспроизвести сценарий чтения, происходящего чаще, чем записи. Это просто неправильный способ сделать это. Вместо этого я хотел бы ввести дополнительные накладные расходы на вашиAdd
иGet
методы , чтобы создать больше шансы раздора (как Ганс предложил ), создать более читаемые потоки , чем потоки записи (для обеспечения более частых операции чтения , чем записи), и удалитьThread.Sleep
вызовы отReadThread
иWriteThread
(которые на самом деле уменьшить разногласия, достигнув противоположного желаемого).Мне нравится то, что вы сделали до сих пор. Но вот несколько проблем, которые я вижу сразу же:
Thread.Sleep
звонки? Это просто завышение времени выполнения на постоянную величину, что искусственно приведет к сходимости результатов производительности.Thread
объектов в код, который измеряется вашимStopwatch
. Создать такой объект нетривиально.Я не знаю, заметите ли вы существенную разницу, когда решите две проблемы, указанные выше. Но я считаю, что их следует рассмотреть до продолжения обсуждения.
источник
Thread
объектов станет незначительным.Вы получите лучшую производительность,
ReaderWriterLockSlim
чем простая блокировка, если заблокируете часть кода, для выполнения которой требуется больше времени. В этом случае читатели могут работать параллельно. ПолучениеReaderWriterLockSlim
занимает больше времени, чем ввод простогоMonitor
. Проверьте моюReaderWriterLockTiny
реализацию на наличие блокировки чтения-записи, которая даже быстрее, чем простой оператор блокировки, и предлагает функции чтения-записи: 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
.источник
Если у вас нет многоядерного оборудования (или, по крайней мере, такого же, как ваша запланированная производственная среда), вы не получите здесь реалистичного теста.
Более разумным тестом было бы продлить срок службы заблокированных операций, поместив небольшую задержку внутри блокировки. Таким образом, вы действительно сможете противопоставить параллелизм, добавленный using,
ReaderWriterLockSlim
и сериализацию, подразумеваемую базовымlock()
.В настоящее время время, затрачиваемое на ваши заблокированные операции, теряется в шуме, создаваемом вызовами сна, происходящими вне блокировок. Общее время в любом случае в основном связано со сном.
Вы уверены, что в вашем реальном приложении будет одинаковое количество операций чтения и записи?
ReaderWriterLockSlim
действительно лучше для случая, когда у вас много читателей и относительно редко пишут. 1 поток записи по сравнению с 3 потоками чтения должныReaderWriterLockSlim
лучше продемонстрировать преимущества, но в любом случае ваш тест должен соответствовать ожидаемому шаблону доступа в реальном мире.источник
Я предполагаю, что это из-за того, что вы спите в ваших читательских и писательских тредах.
Ваш поток чтения имеет спящий режим 500тимс 50 мс, что составляет 25000 большую часть времени.
источник
Когда у вас значительно больше операций чтения, чем записи.
источник