C# ReaderWriterLockSlim何时比简单锁更好?

C# ReaderWriterLockSlim何时比简单锁更好?,c#,.net,multithreading,locking,C#,.net,Multithreading,Locking,我用这段代码在ReaderWriterLock上做了一个非常愚蠢的基准测试,其中读取的频率是写入的4倍: class Program { static void Main() { ISynchro[] test = { new Locked(), new RWLocked() }; Stopwatch sw = new Stopwatch(); foreach ( var isynchro in test ) {

我用这段代码在ReaderWriterLock上做了一个非常愚蠢的基准测试,其中读取的频率是写入的4倍:

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";
    }
}
即使使读的次数比写的次数多20倍,性能仍然(几乎)相同

我做错什么了吗


问候您。

我想这是因为您的读写线程中存在睡眠。

你的读线程有一个500tims 50ms的睡眠时间,在它睡眠的大部分时间是25000编辑2:只需删除
线程。睡眠
调用
ReadThread
WriteThread
,我看到
Locked
的性能优于
RWLocked
。我相信这是一针见血;您的方法太快,不会产生争用。当我将
线程.Sleep(1)
添加到
Get
Add
方法的
Locked
RWLocked
(使用4个读线程和1个写线程)中时,
RWLocked
Locked
的裤子打下来


编辑:好吧,如果我第一次发布这个答案时真的在想,我至少会意识到为什么你要把
线程。Sleep
调用放在那里:你试图重现读比写更频繁的场景。这不是正确的方法。相反,我会为
Add
Get
方法引入额外的开销,以创建更大的争用机会(as),创建更多的读线程而不是写线程(以确保读线程比写线程更频繁),并删除
线程的
Sleep
调用
ReadThread
(这实际上减少了争用,实现了与您想要的相反的效果)


我喜欢你到目前为止所做的一切。但这里有几个问题我一眼就能看到:

  • 为什么要调用
    Thread.Sleep
    呢?这些调用只会使执行时间不断膨胀,这会人为地使性能结果趋于一致
  • 我也不会在由您的
    秒表测量的代码中包含新
    线程
    对象的创建。这不是一个简单的创建对象
  • 我不知道,一旦你解决了上述两个问题,你是否会看到显著的不同。但我认为,在继续讨论之前,应该先解决它们。

    查看这篇文章:


    您的睡眠时间可能足够长,使得锁定/解锁在统计上无关紧要。

    在您的示例中,睡眠意味着通常不存在争用。无争用的锁非常快。为此,您需要争用的锁;如果争用中有写入,它们应该大致相同(
    lock
    可能更快)-但是如果它主要是读取(很少有写入争用),我希望
    ReaderWriterLockSlim
    lock能够执行
    lock


    就我个人而言,我更喜欢这里的另一种策略,使用引用交换-因此读取总是可以在不检查/锁定等的情况下读取。写入将其更改为克隆副本,然后使用
    互锁。CompareExchange
    交换引用(如果另一个线程在此期间更改了引用,则重新应用其更改).

    无竞争的锁的获取时间为微秒,因此,调用
    Sleep
    将使执行时间相形见绌,除非您有多核硬件(或至少与计划的生产环境相同),否则无法在此处进行实际测试

    一个更合理的测试是,通过在锁中放置一个短暂的延迟来延长锁定操作的生命周期。这样,您就可以真正对比使用ReaderWriterLockSlim添加的并行性与basic
    lock()
    所隐含的序列化

    目前,被锁定的操作所花费的时间会在锁外发生的休眠调用所产生的噪音中丢失。在这两种情况下,总时间大多与休眠相关


    你确定你的真实世界应用程序的读写次数相等吗?
    ReaderWriterLockSlim
    对于拥有多个读卡器和相对不常使用的写卡器的情况来说确实更好。1个写卡器线程与3个读卡器线程的对比应该能更好地证明
    ReaderWriterLockSlim
    的好处,但无论如何,你的测试结果都是正确的uld符合您预期的真实访问模式。

    此程序中没有争用。Get和Add方法在几纳秒内执行。多个线程在准确时间命中这些方法的几率非常小


    放入一个Thread.Sleep(1)调用它们,并从线程中移除睡眠以查看差异。

    我自己的测试表明,
    ReaderWriterLockSlim
    的开销大约是普通
    锁的5倍。这意味着RWL要优于普通的旧锁,通常会出现以下情况

    • 读者的数量大大超过了作者
    • 锁必须保持足够长的时间以克服额外的开销

    在大多数实际应用程序中,这两个条件不足以克服额外的开销。具体来说,在您的代码中,锁的持有时间很短,锁的开销可能是主要因素。如果要将这些
    线程.Sleep
    调用移到锁内,则可能会得到d不同的结果。

    如果锁定需要较长时间执行的部分代码,则使用
    ReaderWriterLockSlim
    会比使用简单的锁获得更好的性能。在这种情况下,读卡器可以并行工作。获取
    ReaderWriterLockSlim
    比输入简单的
    监视器花费更多的时间
    
    Locked: 25003ms.
    RW Locked: 25002ms.
    End