84 votes

Quand est ReaderWriterLockSlim mieux qu'une simple serrure?

Je suis en train de faire un bête point de référence sur le ReaderWriterLock avec ce code, où la lecture se produit 4 fois plus souvent que d'écrire:

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 n'obtiens que les deux effectuer plus ou moins de la même façon:

Locked: 25003ms.
RW Locked: 25002ms.
End

Même le lire 20 fois plus souvent que les écritures, la performance est encore (presque) le même.

Suis-je en train de faire quelque chose de mal ici?

Salutations.

112voto

Marc Gravell Points 482669

Dans votre exemple, l'dort veux dire que , généralement, il n'existe aucun conflit. Un uncontended de verrouillage est très rapide. Pour cette question, vous auriez besoin d'un prétendu verrouillage; si il y a écrit dans ce point de vue, ils devraient être à peu près la même (lock peut-être même plus), mais si c'est pour la plupart des lectures (avec une réduction de la contention rarement), je m'attendrais à l' ReaderWriterLockSlim de verrouillage à effectuer l' lock.

Personnellement, je préfère une autre stratégie ici, à l'aide de l'échange - c'est peut toujours lire sans jamais vérifier / verrouillage / etc. Écrit effectuer leur changement d'un cloné copie, puis utilisez Interlocked.CompareExchange de swap de référence (ré-appliquer leur changement si un autre thread muté de la référence dans l'intervalle).

26voto

Brian Gideon Points 26683

Mes propres tests indiquent que ReaderWriterLockSlim a environ 5x la surcharge par rapport à la normale lock. Cela signifie que pour la RWLS à surperformer un simple vieux verrouiller les conditions suivantes devraient généralement être produit.

  • Le nombre de lecteurs de manière significative dépasse les écrivains.
  • Le verrou de tenir assez longtemps pour surmonter la surcharge supplémentaire.

Dans la plupart des applications réelles, ces deux conditions ne sont pas suffisantes pour surmonter cette charge supplémentaire. Dans votre code plus précisément, les serrures sont détenus pour une courte période de temps que le verrouillage de la surcharge sera probablement le facteur dominant. Si vous étiez à déplacer ceux - Thread.Sleep des appels à l'intérieur de la serrure, alors vous auriez probablement obtenir un résultat différent.

18voto

Hans Passant Points 475940

Il n'y a pas de conflit dans ce programme. De l'Obtenir et Ajouter des méthodes pour exécuter en quelques nanosecondes. Les chances que plusieurs threads frapper ces méthodes à l'heure exacte sont extrêmement petite.

Mettre un Fil.Sleep(1) appel à eux et à enlever le sommeil de l'threads pour voir la différence.

12voto

Dan Tao Points 60518

Edit 2: il suffit de retirer le Thread.Sleep appels d' ReadThread et WriteThread, j'ai vu Locked surpasser 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. Quand j'ai ajouté Thread.Sleep(1) de la Get et Add méthodes Locked et RWLocked (et utilisé 4 lire les discussions contre 1 écrire fil), RWLocked battre le pantalon large de l' Locked.


Edit: OK, si j'étais fait la réflexion quand j'ai posté cette réponse, je l'ai réalisé au moins pourquoi vous mettez l' Thread.Sleep des appels dans y est: vous avez été d'essayer de reproduire le scénario de lit qui arrive plus fréquemment qu'à l'écrit. Ce est tout simplement pas la bonne façon de le faire. Au lieu de cela, je voudrais introduire une charge supplémentaire pour votre Add et Get méthodes pour créer une plus grande chance de contention (comme Hans suggéré), de créer plus de lire les discussions que d'écrire des threads (pour s'assurer de plus en plus fréquentes lit que les écritures), et de supprimer l' Thread.Sleep appels d' ReadThread et WriteThread (qui, en fait, de réduire la contention, l'atteinte à l'opposé de ce que vous voulez).


J'aime ce que vous avez fait jusqu'à présent. Mais voici quelques questions, je vois tout de suite la chauve-souris:

  1. Pourquoi l' Thread.Sleep des appels? Ce sont juste gonflant de votre temps d'exécution par une valeur constante, qui va artificiellement faire de la performance résultats convergent.
  2. Je n'ai pas la création de nouvelles Thread objets dans le code qui est mesurée par votre Stopwatch. Ce n'est pas un banal objet à créer.

Si vous voyez une différence significative une fois que vous répondre aux deux questions ci-dessus, je ne sais pas. Mais je crois qu'ils doivent être adressées avant la discussion continue.

3voto

Nelson Rothermel Points 3538

Découvrez cet article: http://blogs.msdn.com/b/pedram/archive/2007/10/07/a-performance-comparison-of-readerwriterlockslim-with-readerwriterlock.aspx

Votre couchages sont probablement assez longtemps que ils font de votre verrouillage/déverrouillage statistiquement non significatif.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X