Why is lock much slower than Monitor.TryEnter?
结果
锁定:85.3微秒
监视器.tryenter:11.0微秒
锁是否扩展为同一代码?
编辑:1000次迭代的结果:锁定:103.3微秒监视器.tryenter:20.2微秒
下面的代码。谢谢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | [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);; } |
我实际上不知道答案,但我认为有必要指出,
If successful, this method acquires an
exclusive lock on the obj parameter.
This method returns immediately,
whether or not the lock is available.
就其价值而言,我只是在我的机器上尝试了您的代码,得到了完全不同的结果:
100次迭代:
100000次迭代:
这严重破坏了我最初的猜测,表明在我的系统中,
事实上,我在与2010年的对比中尝试将.NET 3.5和.NET 4.0作为目标,尽管结果不同,但在每种情况下,
运行100次,每次100000次迭代:锁定:279736.4微秒监视器.tryenter:1366751.5微秒monitor.tryenter(无超时):475107.3微秒监视器。输入:332334.1微秒
运行时版本:4.0.30128.1运行100次,每次100000次迭代:锁定:334273.7微秒监视器.tryenter:1671363.4微秒monitor.tryenter(无超时):531451.8微秒监视器。输入:316693.1微秒
(注意,我也在没有超时的情况下测试了
基于这些结果,我强烈地倾向于相信,通过使用
100太少了,在测试框架中运行可能会使事情倾斜。它还可能(参见注释)与与对象的第一个锁相关联的任何附加成本相关;请尝试:
- 先锁闭一次回路外
- 做更多的迭代
- 在控制台exe中,在命令行中,在释放模式下
另外,请注意,在4.0版中,
但我得到:
1 2 3 4 | lock: 3548ms Monitor.TryEnter: 7008ms Monitor.TryEnter (2): 2947ms Monitor.Enter: 2906ms |
从试验台:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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"); } } |
可以使用.NET Reflector检查生成的IL。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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); } |
可能是因为锁是monitor.enter,而不是monitor.tryenter?
如果你需要速度,那么旋转锁在我的经验中是一个更好的选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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(); } } } } |
在中止和异常情况下,是让事情"自动"工作的一个很好的有用方法。
您只需创建一个spinlock而不是"lock"对象,然后使用:
1 2 3 |
这允许您获得lock()提供的同一行代码,同时还可以处理所需的try finally行为,并对清理对象所发生的事情有更多的控制。
我还支持"Try"案例,该案例将使用代码块编写,其中包含一个额外的if语句:
1 2 3 4 5 | using( theLock = new DisposableSpinLock( myLock, true ) ) { if( theLock.IsLocked ) { // Under Lock and Key } } |
spinlock对于高度争用的锁不是CPU友好型的,因为在这种情况下,spinlock增加了CPU的使用,但是对于那些几乎是同步的,只是偶尔需要为外部引用或偶尔的第二线程访问锁定的锁,这是一个巨大的胜利。
是的,这不是华丽的,但对我来说,旋转锁使我拥有的所有东西都变得更具性能。
http://www.adammil.net/blog/v111_创建_-high-performance_-locks_和_-lock-free_-code_用于_-net_u.html可以很好地了解旋转锁和锁定的总体情况。