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.

67
demandé sur bzlm 2010-11-18 19:51:57

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).

88
répondu Marc Gravell 2010-11-18 17:02:56

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.

19
répondu Brian Gideon 2010-11-18 17:48:16

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.

14
répondu Hans Passant 2010-11-18 17:19:09

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

  1. 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.
  2. Je n'inclurais pas non plus la création de nouveaux objets Thread dans le code qui est mesuré par votre Stopwatch . 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.

9
répondu Dan Tao 2017-05-23 11:47:30

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 /

6
répondu i255 2013-10-05 15:06:12

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.

3
répondu Nelson Rothermel 2010-11-18 17:02:45

non contesté les serrures prennent de l'ordre des microsecondes à acquérir, donc votre temps d'exécution sera éclipsé par vos appels à Sleep .

3
répondu MSN 2010-11-18 17:05:21

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.

3
répondu Steve Townsend 2010-11-18 17:14:03

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

2
répondu Itay Karo 2010-11-18 17:00:25

quand est-ce que ReaderWriterLockSlim est mieux qu'une simple serrure?

quand vous avez beaucoup plus de lectures que d'Écritures.

0
répondu Illidan 2017-07-14 07:16:14