Quand est-ce que ReaderWriterLockSlim est mieux qu'une simple serrure?
je fais un benchmark très stupide sur le ReaderWriterLock avec ce code, où la lecture arrive 4x plus souvent que l'écriture:
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";
}
}
mais je comprends que les deux se produisent plus ou moins de la même manière:
Locked: 25003ms.
RW Locked: 25002ms.
End
même en faisant la lecture 20 fois plus souvent qui écrit, la performance est toujours (presque) la même.
est-ce que je fais quelque chose de mal ici?
cordialement.
10 réponses
dans votre exemple, le sleeps signifie que généralement il n'y a pas de contestation. Une serrure sans surveillance est très rapide. Pour que cela importe, vous auriez besoin d'un prétendu serrure; s'il ya écrit dans cette prétention, ils devraient être à peu près les mêmes ( lock
peut-être même plus rapide) - mais si elle est la plupart du temps lit (avec une écriture affirmation rarement), je m'attend à ce que le ReaderWriterLockSlim
serrure à le lock
.
personnellement, je préfère une autre stratégie ici, en utilisant l'échange de références - ainsi les lectures peuvent toujours lire sans jamais vérifier/verrouiller / etc. Écrit faire leur changement à un cloné copie, puis utiliser Interlocked.CompareExchange
pour échanger la référence (appliquer de nouveau leur changement si un autre fil a muté la référence dans l'intervalle).
mes propres tests indiquent que ReaderWriterLockSlim
a environ 5 fois plus de frais généraux par rapport à une normale lock
. Cela signifie que pour les RWL de dépasser un vieux verrou simple, les conditions suivantes se produiraient généralement.
- le nombre de lecteurs est nettement supérieur à celui des écrivains.
- l'écluse devrait être maintenue assez longtemps pour éviter les frais généraux supplémentaires.
Dans la plupart réel applications ces deux conditions ne sont pas suffisantes pour surmonter ces frais généraux supplémentaires. Dans votre code en particulier, les serrures sont maintenues pendant une période de temps si courte que la serrure au-dessus de la tête sera probablement le facteur dominant. Si vous deviez déplacer ces appels Thread.Sleep
à l'intérieur de la serrure, vous obtiendriez probablement un résultat différent.
il n'y a pas de contestation dans ce programme. Les méthodes Get et Add s'exécutent en quelques nanosecondes. Les chances que plusieurs threads frapper ces méthodes à l'heure exacte sont extrêmement petite.
mettre un fil.Sleep (1) appelez-les et retirez le sommeil des fils pour voir la différence.
Edit 2 : simplement supprimer les appels Thread.Sleep
de ReadThread
et WriteThread
, j'ai vu Locked
outperform RWLocked
. Je crois que Hans a frappé le clou sur la tête ici; vos méthodes sont trop vite et de ne pas créer de conflit. Lorsque j'ai ajouté Thread.Sleep(1)
aux Get
et Add
méthodes de Locked
et RWLocked
(et utilisé 4 fils de lecture contre 1 fil d'écriture), RWLocked
battre le pantalon de Locked
.
Edit : OK, si j'étais en fait pensant quand j'ai posté cette réponse pour la première fois, j'aurais réalisé au moins pourquoi vous avez mis les appels Thread.Sleep
là-dedans: vous essayiez de reproduire le scénario de lit se produit plus souvent qu'écrit. Ce est tout simplement pas la bonne façon de le faire. Au lieu de cela, je voudrais introduire des frais généraux supplémentaires à votre Add
et les méthodes Get
pour créer une plus grande chance de discorde (comme Hans suggéré ), créer plus de fils de lecture que de fils d'écriture (pour s'assurer lit plus fréquemment que écrit), et supprimer les Thread.Sleep
appels de ReadThread
et WriteThread
(qui en fait réduire affirmation, atteindre le contraire de ce que vous voulez).
j'aime ce que vous avez fait jusqu'à présent. Mais voici quelques questions que je
- pourquoi les appels
Thread.Sleep
? Ce sont juste gonfler vos délais d'exécution d'une quantité constante, qui va artificiellement faire converger les résultats de performance. - Je n'inclurais pas non plus la création de nouveaux objets
Thread
dans le code qui est mesuré par votreStopwatch
. Ce n'est pas un objet insignifiant à créer.
Si vous allez voir un important différence une fois que vous avez abordé les deux questions ci-dessus, je ne sais pas. Mais je pense qu'il faut les aborder avant que la discussion ne se poursuive.
vous obtiendrez de meilleures performances avec ReaderWriterLockSlim
qu'une simple serrure si vous verrouillez une partie du code qui nécessite plus de temps à exécuter. Dans ce cas, les lecteurs peuvent travailler en parallèle. L'acquisition d'un ReaderWriterLockSlim
prend plus de temps qu'un simple Monitor
. Consultez mon implémentation ReaderWriterLockTiny
pour une serrure readers-writer qui est encore plus rapide qu'un simple lock statement et offre une fonctionnalité readers-writer: http://i255.wordpress.com/2013/10/05/fast-readerwriterlock-for-net /
consultez cet article: http://blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx
vos nuits sont probablement assez longues pour rendre votre verrouillage/déverrouillage statistiquement insignifiant.
non contesté les serrures prennent de l'ordre des microsecondes à acquérir, donc votre temps d'exécution sera éclipsé par vos appels à Sleep
.
sauf si vous avez du matériel multicore (ou au moins le même que votre environnement de production prévu), vous n'obtiendrez pas de test réaliste ici.
un test plus raisonnable serait de prolonger la durée de vie de vos opérations verrouillées en mettant un court délai à l'intérieur de la serrure. De cette façon, vous devriez vraiment être en mesure de comparer le parallélisme ajouté en utilisant ReaderWriterLockSlim
versus la sérialisation implicite par lock()
de base .
Actuellement, le temps pris par vos opérations qui sont verrouillées sont perdus dans le bruit généré par les appels de sommeil qui se produisent à l'extérieur des serrures. Dans les deux cas, la durée totale est principalement liée au Sommeil.
êtes-vous sûr que votre application du monde réel aura le même nombre de lectures et d'Écritures? ReaderWriterLockSlim
est vraiment mieux pour le cas où vous avez beaucoup de lecteurs et écrivains relativement peu fréquents. 1 thread d'auteur par rapport à 3 threads de lecteur devrait démontrer ReaderWriterLockSlim
avantages mieux, mais dans tous les cas votre test devrait correspondre à votre modèle d'accès réel attendu.
je suppose que c'est à cause du sommeil que vous avez en vous les fils de lecteur et d'écrivain.
Votre fil de lecture a un 500tims 50ms sommeil qui est de 25000 la plupart du temps, il dort
quand est-ce que ReaderWriterLockSlim est mieux qu'une simple serrure?
quand vous avez beaucoup plus de lectures que d'Écritures.