Ich mache mit diesem Code einen sehr dummen Benchmark auf dem ReaderWriterLock, bei dem das Lesen viermal häufiger erfolgt als das Schreiben:
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";
}
}
Aber ich verstehe, dass beide mehr oder weniger gleich abschneiden:
Locked: 25003ms.
RW Locked: 25002ms.
End
Selbst wenn das Lesen 20 Mal häufiger als das Schreiben erfolgt, ist die Leistung immer noch (fast) gleich.
Mache ich hier etwas falsch?
Mit freundlichen Grüßen.
c#
.net
multithreading
locking
vtortola
quelle
quelle
Antworten:
In Ihrem Beispiel bedeutet der Schlaf, dass im Allgemeinen keine Konflikte bestehen. Eine unbestrittene Sperre ist sehr schnell. Damit dies von Bedeutung ist, benötigen Sie eine umstrittene Sperre. Wenn sich in dieser Konkurrenz Schreibvorgänge befinden , sollten diese ungefähr gleich sein (
lock
möglicherweise sogar schneller). Wenn es sich jedoch hauptsächlich um Lesevorgänge handelt (bei Schreibkonflikten selten), würde ich erwarten, dass dieReaderWriterLockSlim
Sperre die Leistung übertrifftlock
.Persönlich bevorzuge ich hier eine andere Strategie, bei der der Referenzaustausch verwendet wird. Daher können Lesevorgänge immer gelesen werden, ohne jemals zu überprüfen / zu sperren / usw. Schreibvorgänge nehmen ihre Änderung an einer geklonten Kopie vor und verwenden sie dann
Interlocked.CompareExchange
zum Austauschen der Referenz (erneutes Anwenden ihrer Änderung, wenn ein anderer Thread verwendet wird die Referenz in der Zwischenzeit mutiert).quelle
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
und nur einvolatile
Feld zu verwenden und es einfach zuzuweisen - aber Wenn Sie sicher sein müssen , dass Ihr Wechselgeld nicht vollständig verloren gegangen ist,Interlocked
ist dies idealMeine eigenen Tests haben ergeben, dass
ReaderWriterLockSlim
der Overhead im Vergleich zu normalen Tests etwa das Fünffache beträgtlock
. Das bedeutet, dass die RWLS eine einfache alte Sperre übertreffen und im Allgemeinen die folgenden Bedingungen auftreten würden.In den meisten realen Anwendungen reichen diese beiden Bedingungen nicht aus, um diesen zusätzlichen Aufwand zu überwinden. In Ihrem Code werden die Sperren so kurz gehalten, dass der Overhead der Sperren wahrscheinlich der dominierende Faktor ist. Wenn Sie diese
Thread.Sleep
Anrufe innerhalb des Schlosses verschieben würden, würden Sie wahrscheinlich ein anderes Ergebnis erhalten.quelle
Es gibt keinen Streit in diesem Programm. Die Methoden Get und Add werden in wenigen Nanosekunden ausgeführt. Die Wahrscheinlichkeit, dass mehrere Threads genau zum richtigen Zeitpunkt auf diese Methoden treffen, ist verschwindend gering.
Fügen Sie einen Thread.Sleep (1) -Aufruf ein und entfernen Sie den Schlaf aus den Threads, um den Unterschied zu erkennen.
quelle
Bearbeiten 2 : Entfernen Sie einfach die
Thread.Sleep
Anrufe vonReadThread
undWriteThread
, ich sahLocked
OutperformRWLocked
. Ich glaube, Hans hat hier den Nagel auf den Kopf getroffen; Ihre Methoden sind zu schnell und verursachen keine Konflikte. Wenn ichThread.Sleep(1)
zuGet
undAdd
Methoden vonLocked
und hinzugefügt habeRWLocked
(und 4 Lese-Threads gegen 1 Schreib-Thread verwendet habe), habe ichRWLocked
die Hose ausgezogenLocked
.Bearbeiten : OK, wenn ich tatsächlich darüber nachgedacht hätte, als ich diese Antwort zum ersten Mal gepostet habe, wäre mir zumindest klar geworden, warum Sie die
Thread.Sleep
Anrufe dort getätigt haben: Sie haben versucht, das Szenario zu reproduzieren, dass Lesevorgänge häufiger als Schreibvorgänge stattfinden. Dies ist einfach nicht der richtige Weg, dies zu tun. Stattdessen würde ich IhrenAdd
und IhrenGet
Methoden zusätzlichen Overhead hinzufügen, um eine größere Wahrscheinlichkeit von Konflikten zu schaffen (wie Hans vorgeschlagen hat ), mehr Lesethreads als Schreibthreads zu erstellen ( um häufigeres Lesen als Schreiben zu gewährleisten) und dieThread.Sleep
Aufrufe vonReadThread
undWriteThread
(die tatsächlich ) entfernen Reduzieren Sie Konflikte und erreichen Sie das Gegenteil von dem, was Sie wollen.Mir gefällt, was Sie bisher gemacht haben. Aber hier sind ein paar Probleme, die ich auf Anhieb sehe:
Thread.Sleep
Anrufe? Diese erhöhen lediglich Ihre Ausführungszeiten um einen konstanten Betrag, wodurch die Leistungsergebnisse künstlich konvergieren.Thread
Objekte in den Code aufnehmen, der von Ihnen gemessen wirdStopwatch
. Das ist kein triviales Objekt.Ob Sie einen signifikanten Unterschied feststellen werden, wenn Sie die beiden oben genannten Probleme ansprechen, weiß ich nicht. Aber ich glaube, sie sollten angesprochen werden, bevor die Diskussion fortgesetzt wird.
quelle
Thread
Objekte allmählich unbedeutend.Sie erzielen
ReaderWriterLockSlim
eine bessere Leistung als mit einer einfachen Sperre, wenn Sie einen Teil des Codes sperren, dessen Ausführung länger dauert. In diesem Fall können die Leser parallel arbeiten. Das Erwerben von aReaderWriterLockSlim
dauert länger als das Eingeben eines einfachenMonitor
. Überprüfen Sie meineReaderWriterLockTiny
Implementierung auf eine Readers-Writer-Sperre, die noch schneller als eine einfache Lock-Anweisung ist und eine Readers-Writer-Funktionalität bietet: http://i255.wordpress.com/2013/10/05/fast-readerwriterlock-for-net/quelle
Lesen Sie diesen Artikel: http://blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx
Ihr Schlaf ist wahrscheinlich lang genug, um Ihr Sperren / Entsperren statistisch unbedeutend zu machen.
quelle
Unbestrittene Sperren müssen in der Größenordnung von Mikrosekunden erfasst werden, sodass Ihre Ausführungszeit durch Ihre Aufrufe an in den Schatten gestellt wird
Sleep
.quelle
Wenn Sie nicht über Multicore-Hardware verfügen (oder zumindest mit Ihrer geplanten Produktionsumgebung), erhalten Sie hier keinen realistischen Test.
Ein sinnvollerer Test wäre, die Lebensdauer Ihrer gesperrten Vorgänge zu verlängern, indem Sie eine kurze Verzögerung in das Schloss einfügen. Auf diese Weise sollten Sie wirklich in der Lage sein, die hinzugefügte Parallelität mit
ReaderWriterLockSlim
der durch basic implizierten Serialisierung zu vergleichenlock()
.Derzeit geht die Zeit, die Ihre gesperrten Vorgänge benötigen, durch das Rauschen verloren, das durch die Sleep-Anrufe außerhalb der Sperren erzeugt wird. Die Gesamtzeit ist in beiden Fällen hauptsächlich schlafbezogen.
Sind Sie sicher, dass Ihre reale App die gleiche Anzahl an Lese- und Schreibvorgängen hat?
ReaderWriterLockSlim
ist wirklich besser für den Fall, dass Sie viele Leser und relativ seltene Schriftsteller haben. 1 Writer-Thread im Vergleich zu 3 Reader-Threads sollten dieReaderWriterLockSlim
Vorteile besser demonstrieren , aber in jedem Fall sollte Ihr Test Ihrem erwarteten realen Zugriffsmuster entsprechen.quelle
Ich denke, das liegt an dem Schlaf, den Sie in Ihren Leser- und Schreiber-Threads haben.
Ihr gelesener Thread hat einen Schlaf von 500 Tim und 50 ms, was 25000 entspricht. Die meiste Zeit schläft er
quelle
Wenn Sie deutlich mehr Lese- als Schreibvorgänge haben.
quelle