为什么锁比Monitor.TryEnter慢得多?

时间:2010-03-10 12:26:57

标签: c#

结果

锁定:85.3微秒

Monitor.TryEnter:11.0微秒

锁是否扩展为相同的代码?

编辑:1000次迭代的结果: 锁定:103.3微秒 Monitor.TryEnter:20.2微秒

以下代码。感谢

    [Test]
    public void Lock_Performance_Test()
    {
        const int lockIterations = 100;

        Stopwatch csLock = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; )
        {
            lock (object1)
            {
                i++;
            }
        }
        csLock.Stop();

        Stopwatch csMonitor = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; )
        {
            if (Monitor.TryEnter(object1, TimeSpan.FromSeconds(10)))
            {
                try
                {
                    i++;
                }
                finally
                {
                    Monitor.Exit(object1);
                }
            }
        }
        csMonitor.Stop();

        Console.WriteLine("Lock: {0:f1} microseconds", csLock.Elapsed.Ticks / 10M);
        Console.WriteLine("Monitor.TryEnter: {0:f1} microseconds", csMonitor.Elapsed.Ticks / 10M);;
    }

5 个答案:

答案 0 :(得分:30)

我实际上并不知道答案,但我觉得指出lockMonitor.TryEnter 在功能上等效是很重要的。来自the MSDN documentation on Monitor.TryEnter

  

如果成功,此方法获取   对obj参数的独占锁定。   此方法立即返回,   锁是否可用。

lock语句类似于Monitor.Enter 可能阻止。当然,在您的示例代码中,不应存在任何阻塞问题;但是我会下注,因为lock提供了阻止,它比TryEnter做了更多的工作(可能)。


对于它的价值,我只是在我的机器上尝试了你的代码并且完全不同的结果:

100次迭代:
lock:4.4微秒
Monitor.TryEnter:16.1微秒
Monitor.Enter:3.9微秒

100000次迭代:
lock:2872.5微秒
Monitor.TryEnter:5226.6微秒
Monitor.Enter:2432.9微秒

这严重破坏了我最初的猜测,并表明,在我的系统上,lock(与Monitor.Enter大致相同)实际上优于 Monitor.TryEnter


事实上,我在VS 2010中尝试针对.NET 3.5和.NET 4.0,虽然结果不同,但在每种情况下lock确实优于Monitor.TryEnter

运行时版本:2.0.50727.3603

每次跑100次,每次100000次迭代:
锁定:279736.4 微秒
Monitor.TryEnter:1366751.5 微秒
Monitor.TryEnter(无超时):475107.3微秒
Monitor.Enter:332334.1微秒

运行时版本:4.0.30128.1

每次跑100次,每次100000次迭代:
锁定:334273.7 微秒
Monitor.TryEnter:1671363.4 微秒
Monitor.TryEnter(无超时):531451.8微秒
Monitor.Enter:316693.1微秒

(注意我还测试Monitor.TryEnter没有超时,因为我同意Marc的说法,调用TimeSpan.FromSeconds几乎肯定会减慢Monitor.TryEnter的时间 - 而且这些测试支持 - - 虽然这很奇怪,因为在你的情况下,显然lock 显着变慢。)

基于这些结果,我强烈倾向于认为通过使用Test属性运行此代码会影响您的测量执行时间。无论是那个代码还是这个代码都比我预期的更依赖于机器。

答案 1 :(得分:4)

100太少了,在测试框架中运行可能会扭曲事物。也可能(参见注释)与针对对象的第一个锁相关的任何额外成本相关;尝试:

  • 首先在循环外锁定一次
  • 做更多的迭代
  • 在控制台exe中,在命令行中,处于发布模式

另请注意,4.0 lock Monitor.Enter(object) - 因此预计4.0中会有不同的结果。

但我明白了:

lock: 3548ms
Monitor.TryEnter: 7008ms
Monitor.TryEnter (2): 2947ms
Monitor.Enter: 2906ms

来自试验台:

using System;
using System.Diagnostics;
using System.Threading;
static class Program {
    static void Main()
    {
        const int lockIterations = 50000000;
        object object1 = new object();
        lock (object1) { Console.WriteLine("First one has to pay an extra toll"); }
        Stopwatch csLock = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; ) {
            lock (object1) { i++; }
        }
        csLock.Stop();
        Console.WriteLine("lock: " + csLock.ElapsedMilliseconds + "ms");

        Stopwatch csMonitorTryEnter = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; ) {
            if (Monitor.TryEnter(object1, TimeSpan.FromSeconds(10))) {
                try { i++; } finally { Monitor.Exit(object1); }
            }
        }
        csMonitorTryEnter.Stop();
        Console.WriteLine("Monitor.TryEnter: " + csMonitorTryEnter.ElapsedMilliseconds + "ms");

        csMonitorTryEnter = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; ) {
            if (Monitor.TryEnter(object1, 10000)) {
                try { i++; } finally { Monitor.Exit(object1); }
            }
        }
        csMonitorTryEnter.Stop();
        Console.WriteLine("Monitor.TryEnter (2): " + csMonitorTryEnter.ElapsedMilliseconds + "ms");

        Stopwatch csMonitorEnter = Stopwatch.StartNew();
        for (int i = 0; i < lockIterations; ) {
            Monitor.Enter(object1);
            try { i++; } finally { Monitor.Exit(object1); }
        }
        csMonitorEnter.Stop();
        Console.WriteLine("Monitor.Enter: " + csMonitorEnter.ElapsedMilliseconds + "ms");
    }
}

答案 2 :(得分:3)

可能是因为锁是Monitor.Enter,而不是Monitor.TryEnter?

答案 3 :(得分:3)

您可以使用.NET反射器检查生成的IL。 lock关键字使用Monitor.Enter代替Monitor.TryEnter - 这是您问题的简答。以下是代码在反汇编并转换回C#时的样子:

public void Lock_Performance_Test()
{
    Stopwatch csLock = Stopwatch.StartNew();
    int i = 0;
    while (i < 100)
    {
        object CS$2$0000;
        bool <>s__LockTaken0 = false;
        try
        {
            Monitor.Enter(CS$2$0000 = this.object1, ref <>s__LockTaken0);
            i++;
        }
        finally
        {
            if (<>s__LockTaken0)
            {
                Monitor.Exit(CS$2$0000);
            }
        }
    }
    csLock.Stop();
    Stopwatch csMonitor = Stopwatch.StartNew();
    i = 0;
    while (i < 100)
    {
        if (Monitor.TryEnter(this.object1, TimeSpan.FromSeconds(10.0)))
        {
            try
            {
                i++;
            }
            finally
            {
                Monitor.Exit(this.object1);
            }
        }
    }
    csMonitor.Stop();
    Console.WriteLine("Lock: {0:f1} microseconds", csLock.Elapsed.Ticks / 10M);
    Console.WriteLine("Monitor.TryEnter: {0:f1} microseconds", csMonitor.Elapsed.Ticks / 10M);
}

答案 4 :(得分:0)

如果你需要速度这样做,那么根据我的经验,SpinLock是一个更好的选择。

public class DisposableSpinLock : IDisposable {
    private SpinLock mylock;
    private bool isLocked;

    public DisposableSpinLock( SpinLock thelock ) {
        this.mylock = thelock;
        mylock.Enter( ref isLocked );
    }

    public DisposableSpinLock(  SpinLock thelock, bool tryLock) {
        this.mylock = thelock;
        if( tryLock ) {
            mylock.TryEnter( ref isLocked );
        } else {
            mylock.Enter( ref isLocked );
        }
    }

    public bool IsLocked { get { return isLocked; } }

    public void Dispose() {
        Dispose( true );
        GC.SuppressFinalize( this );
    }

    protected virtual void Dispose( bool disposing ) {
        if( disposing ) {
            if( isLocked ) {
                mylock.Exit();
            }
        }
    }
}

是一种非常有用的方法,可以让事情自动发挥作用#34;自动&#34;在中止和例外情况下。

你可以创建一个SpinLock而不是&#34; lock&#34;对象,然后使用:

using( new DisposableSpinLock( myLock ) ) {
     // Under lock and key...
}

这允许您获得lock()提供的相同单行代码,同时还处理所需的try {} finally {}行为,并且可以更好地控制清理对象所发生的事情。

我也支持&#34;尝试&#34;将使用带有额外if语句的代码块编写的case:

using( theLock = new DisposableSpinLock( myLock, true ) ) {
    if( theLock.IsLocked ) {
        // Under Lock and Key
    }
}

对于高度争用的锁,SpinLock不是CPU友好的,因为在这种情况下增加了SpinLock的CPU使用,但是对于几乎同步的锁并且偶尔需要锁定外部引用或偶尔的第二线程访问,这是一场大胜。

是的,这并不华丽,但对我来说,SpinLocks已经让我所拥有的轻微争用锁的性能更加高效。

http://www.adammil.net/blog/v111_Creating_High-Performance_Locks_and_Lock-free_Code_for_NET_.html可以很好地了解自旋锁和整体锁定。