我看到了jeffery richter video(点击查看确切的行),他说:
使用Monitor.Enter和Monitor.Lock总是更好 事件等待句柄或信号量等因为它们(monitor.X)使用内核对象,但如果存在争用,它们仅使用它们。并且如果没有争用,他们就不会使用这些对象。
我可能会在这里遗漏一些东西,但是当我这样做时:
lock(myObj)
{
...
}
我推测可能有多个线程想要进入关键部分。
所以,根据上面的信息,如果没有争用,锁将不会被使用? (如果另一个线程即将进入1毫秒后怎么办?)
答案 0 :(得分:3)
是的,Monitor类在CLR中得到了很好的优化。如果没有其他线程已经拥有监视器,那么<em>非常便宜。您甚至不需要支付额外的存储空间,锁定状态存储在Object中的一个字段中,每个对象已经可用,该对象标题的一部分。
Monitor.Enter()方法通过首先检查锁是否已由同一线程拥有来避免输入操作系统代码。这使得它可以重入,如果是这种情况,它只会增加锁定计数。它接下来尝试使用Interlocked.CompareExchange()的等价物来获取锁,这是任何处理器上非常便宜的原语。它的x86版本值得注意的是根本没有实际使用总线锁,你可以在this answer中看到它的代码。
如果这不起作用,那么操作系统需要介入,因为现在重要的是它可以使线程上下文切换在锁被释放时唤醒线程。 Windows非常赞成选择等待操作系统同步对象的线程,这样可以确保它尽快重新开始运行。底层对象是一个简单的事件,一个非常便宜的OS对象。它还处理公平,等待线程被放入队列并以先进先出顺序释放。我在this answer中记录了基础CLR代码。
答案 1 :(得分:2)
lock
语句只是语法糖,它使用Monitor.Enter
和Monitor.Exit
作为其实现。
Monitor
函数本身是使用Condition Variables实现的。它们的实现意味着它们不需要分配内核对象,除非实际存在争用锁。当发生这种情况时,他们必须继续并分配内核对象。
即使存在锁争用,它们也不会立即分配内核对象。相反,他们“旋转”(只是坐在一个紧紧的循环中一会儿),希望锁定变得自由。只有当它没有变得空闲时,它才会继续分配/使用内核对象。
请注意,某些新的同步类(例如ManualResetEventSlim)也采用了这种方法。 (一般来说,任何带有“Slim”的同步类都会这样做。)
直接回答关于lock
的问题:是的,如果没有争用,或者争用只持续很短的时间,那么为了使用内核对象,将不会转换到内核模式。只有当争用持续时间超过一小会,才会转换到内核模式。
答案 2 :(得分:2)
所以,根据上面的信息,如果没有争用,锁将不会被使用? (如果另一个线程即将进入1毫秒后怎么办?)
正确。然后是争用,另一个线程必须进入内核。此外,具有锁定的线程在解锁时也必须进入内核。
操作类似:
锁定:
尝试以原子方式将用户空间锁定变量从解锁设置为锁定。如果我们这样做,请停止,我们已经完成了。
增加用户空间争用次数。
将内核空间锁定设置为已锁定。
尝试以原子方式将用户空间锁定变量从解锁设置为锁定。如果我们这样做,减少用户空间争用计数并停止,我们就完成了。
在内核锁上的内核中阻塞。
转到第3步。
解锁:
以原子方式将用户空间锁定变量从锁定设置为解锁。
如果用户空间争用计数为零,请停止,我们已完成。
将内核锁设置为解锁。
注意如果没有争用,锁定操作仅涉及步骤1,解锁操作仅涉及步骤1和2,所有这些操作都发生在用户空间中。