Je fais une référence très stupide sur le ReaderWriterLock avec ce code, où la lecture se produit 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 fonctionnent 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 qu’elle écrit, la performance est toujours (presque) la même.
Est-ce que je fais quelque chose de mal ici?
Sincères amitiés.
Dans votre exemple, les mises en sommeil signifient que généralement, il n'y a pas de conflit. Un verrou non contrôlé est très rapide. Pour que cela soit important, il vous faudrait un verrou contended; s'il y a écrit dans ce conflit, ils devraient être à peu près identiques (lock
peut même être plus rapide) - mais s'il s'agit de most lit (avec un conflit d'écriture rarement), je m'attendrais au verrou ReaderWriterLockSlim
surpasser la lock
.
Personnellement, je préfère une autre stratégie ici, l’utilisation de la permutation de références. Les lectures peuvent donc toujours lire sans même vérifier/verrouiller/etc. Les écritures changent en une copie cloned, puis utilisent Interlocked.CompareExchange
pour permuter la référence (réapplication). leur changement si un autre thread a muté la référence dans l’intervalle).
Mes propres tests indiquent que ReaderWriterLockSlim
a environ 5 fois les frais généraux par rapport à une lock
normale. Cela signifie que, pour que le RWLS surpasse un vieux verrou ordinaire, les conditions suivantes seraient généralement remplies.
Dans la plupart des applications réelles, ces deux conditions ne suffisent pas pour surmonter cette surcharge supplémentaire. Dans votre code en particulier, les verrous sont conservés pendant une période de temps si courte que le temps système de verrouillage sera probablement le facteur dominant. Si vous déplaciez ces appels Thread.Sleep
à l'intérieur du verrou, vous obtiendrez probablement un résultat différent.
Il n'y a pas de conflit dans ce programme. Les méthodes Get et Add s'exécutent en quelques nanosecondes. Les chances que plusieurs threads atteignent ces méthodes à l'heure exacte sont infimes.
Mettez un Thread.Sleep (1) appelez-les et supprimez le sommeil des threads pour voir la différence.
Edit 2: En supprimant simplement les appels Thread.Sleep
de ReadThread
et WriteThread
, j'ai vu Locked
surpasser RWLocked
. Je crois que Hans a mis le doigt sur la tête ici; vos méthodes sont trop rapides et ne créent pas de conflit. Lorsque j'ai ajouté Thread.Sleep(1)
aux méthodes Get
et Add
de Locked
et RWLocked
(et utilisé 4 threads en lecture contre 1 en écriture), RWLocked
a dépassé le seuil de Locked
.
Edit: OK, si j’avais eu en train de penser à lorsque j’ai posté cette réponse pour la première fois, j’aurais au moins réalisé pourquoi vous avez mis les appels Thread.Sleep
ici: vous essayez de reproduire le scénario de lectures plus fréquentes que d’écritures. Ce n'est tout simplement pas la bonne façon de faire cela. Au lieu de cela, j'introduirais une surcharge supplémentaire dans vos méthodes Add
et Get
pour créer une plus grande probabilité de conflit (en tant que Hans suggéré ), créer plus de threads lus que d'écriture (pour assurer des lectures plus fréquentes que des écritures), et supprimez les appels Thread.Sleep
de ReadThread
et WriteThread
(ce qui en fait réduit les conflits, obtenant ainsi le contraire de ce que vous voulez).
J'aime ce que vous avez fait jusqu'à présent. Mais voici quelques problèmes que je vois d’emblée:
Thread.Sleep
? Cela ne fait que gonfler vos temps d'exécution d'un montant constant, ce qui va artificiellement faire converger les résultats de performance.Thread
dans le code mesuré par votre Stopwatch
. Ce n'est pas un objet trivial à créer.Je ne sais pas si vous constaterez une différence significative une fois que vous aurez abordé les deux problèmes mentionnés ci-dessus. Mais je crois qu’il faut les aborder avant que la discussion ne continue.
ReaderWriterLockSlim
permet d'obtenir de meilleures performances qu'un simple verrouillage si vous verrouillez une partie du code qui a besoin de plus de temps pour s'exécuter. Dans ce cas, les lecteurs peuvent travailler en parallèle. L'acquisition d'une ReaderWriterLockSlim
prend plus de temps que d'entrer une simple Monitor
. Vérifiez ma mise en œuvre ReaderWriterLockTiny
pour un verrou lecteurs-écrivain qui est encore plus rapide qu'une simple instruction de verrouillage et offre une fonctionnalité lecteurs-écrivain: http://i255.wordpress.com/2013/10/05/fast-readerwriterlock-for- net/
Découvrez cet article: http://blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx
Vos nuits sont probablement suffisamment longues pour rendre votre verrouillage/déverrouillage statistiquement insignifiant.
Uncontested les verrous prennent en ordre de microsecondes à acquérir, votre temps d'exécution sera donc réduit au minimum par vos appels à Sleep
.
Sauf si vous disposez d'un matériel multicœur (ou du moins identique à votre environnement de production prévu), vous ne réaliserez pas de test réaliste ici.
Un test plus judicieux serait de prolonger la durée de vie de vos opérations verrouillées en mettant un bref délai à l'intérieur le verrou. De cette façon, vous devriez vraiment pouvoir contraster le parallélisme ajouté en utilisant ReaderWriterLockSlim
et la sérialisation impliquée par lock()
de base.
Actuellement, le temps pris par vos opérations verrouillées est perdu dans le bruit généré par les appels de veille qui se produisent en dehors des verrous. Dans les deux cas, le temps total est principalement lié au sommeil.
Êtes-vous sûr que votre application réelle aura un nombre égal de lectures et d'écritures? ReaderWriterLockSlim
est vraiment préférable dans le cas où vous avez beaucoup de lecteurs et d'écrivains relativement peu fréquents. Un thread d'écriture contre trois threads de lecteur devraient mieux démontrer les avantages de ReaderWriterLockSlim
, mais dans tous les cas, votre test doit correspondre au modèle d'accès réel attendu.
Je suppose que cela est dû au sommeil que vous avez en vous, lecteurs et écrivains.
Votre fil de lecture a une durée de sommeil de 500 ms 50ms, soit 25 000 La plupart du temps, il dort
Quand ReaderWriterLockSlim est-il meilleur qu'un simple verrou?
Lorsque vous avez beaucoup plus de lectures que d'écritures.