2013-03-16 88 views
1

我看到杰弗里里氏video(点击查看确切的行),他说:争用资源 - 不总是使用锁?

它始终是更好地使用Monitor.Enter和Monitor.Lock过 事件等待句柄或信号等原因他们(monitor.X)使用内核对象,但他们只有使用它们,如果有争用。和如果没有争用,他们不使用这些对象。

我也许在这里的东西,但是当我做:

lock(myObj) 
{ 
... 
} 

推测,有可能是谁想要进入临界区的多个线程。
那么,根据上面的信息,如果没有争用,锁不会被使用? (如果另一个线程即将输入1毫秒后怎么办?)

+0

你为什么在报价中强调这些单词?他强调他们吗? – 2013-03-16 09:06:04

+0

@AshBurlaczenko我相信这会帮助其他人看到我的问题所涉及的重要词汇(对我而言)。 (你对此感到不安吗?欢迎您将其删除) – 2013-03-16 09:10:46

+0

只是,如果你引用某人的话,你应该把它放在他们说的话。如果你认为这有帮助,那很好。 – 2013-03-16 09:12:38

回答

2

那么,根据上面的信息,如果没有争用,锁不会被使用? (如果另一个线程即将输入1 ms后会怎样?)

正确。然后有争用,另一个线程将不得不进入内核。此外,拥有该锁的线程在解锁时也必须进入内核。

的操作那种看起来像这样:

锁:

  1. ,尝试以原子设置从解锁用户空间锁变量来锁定。如果我们这样做,停下来,我们完成了。

  2. 递增用户空间争用计数。

  3. 设置内核空间锁定为锁定状态。

  4. 尝试自动设置用户空间锁定变量从解锁到锁定。如果我们这样做,减少用户空间争用计数并停止,我们就完成了。

  5. 在内核中锁定内核锁。

  6. 转到步骤3。

解锁:

  1. 原子方式设置从锁定的用户空间锁变量解锁。

  2. 如果用户空间争用计数为零,请停止,我们就完成了。

  3. 设置内核锁定为解锁。

注意如果没有争用,锁定操作只涉及步骤1,解锁操作只涉及步骤1和2,所有这些操作都发生在用户空间中。

+0

请澄清:起初,在时间T没有争用!因为只有一个赢家!首先获得锁定的线程。所以如果我是赢家,我怎么知道是否有另一个线程后来呢?我的意思是,作为一个赢家,我会永远看到 - 没有争论!只有第二个线程才会看到资源已被占用......请你解释一下吗? – 2013-03-16 11:44:11

+0

@RoyiNamir:如果另一个线程在解锁时晚点来临,赢家只关心。在我的示例实现中,失败线程通过增加解锁操作中检查的争用计数来告诉获胜者。 (请参阅锁定的步骤2和解锁的步骤2.)获胜(无争用)锁定仅为步骤1.失败(争用)锁定为1,2,3,4,5,6,3,4。获胜(没有争用)解锁只是步骤1和2.失败(争用)解锁是1,2,3。没有任何“获胜”路径有任何内核操作。 – 2013-03-16 11:45:33

+0

谢谢。所以,jeffery其实说的是:如果线程(第1行)试图将_varlock从_unlock_设置为_lock_并成功,那么实际上DONT LOCK,即:不使用内核对象。但如果它没有这样做,所以进入锁定模式...正确? – 2013-03-16 11:52:37

2

lock声明只是语法糖,它使用Monitor.EnterMonitor.Exit作为其实现。

Monitor该函数本身正在使用Condition Variables实现。它们的实现意味着它们不需要分配内核对象,除非实际上存在争用锁的情况。发生这种情况时,他们必须继续并分配一个内核对象。

即使存在锁争用,它们也不会立即分配内核对象。相反,他们会“旋转”(只是短暂地坐着一圈),希望锁定变得自由。只有当它没有变得自由时,它才会继续分配/使用内核对象。

请注意,一些新的同步类,如ManualResetEventSlim也采用这种方法。 (一般地,在端部用“苗条”任何同步类来实现的。)

see this thread另外。

要直接回答你关于lock的问题:是的,如果没有争用,或者争用只持续很短时间,那么为了使用内核对象将不会转换到内核模式。只有当争用持续一段时间后才会发生向内核模式的转换。

3

是的,Monitor类在CLR中进行了很好的优化。它是非常便宜便宜,如果没有其他线程已经拥有监视器。你甚至不支付额外的存储空间,锁定状态被存储在Object中的一个字段中,每个对象都有可用的对象头部分。

Monitor.Enter()方法通过首先检查锁是否已被同一线程拥有,从而避免输入OS代码。这使得它重新进入,如果是这样的话,它只会增加锁计数。接下来试图通过使用Interlocked.CompareExchange()这个等价于任何处理器上非常便宜的基元来获得锁。它的x86版本值得注意,因为实际上根本没有使用总线锁,您可以在this answer中看到它的代码。

如果这样做没有起作用,那么操作系统需要介入,因为现在重要的是它可以使线程上下文切换在锁释放时唤醒线程备份。 Windows非常喜欢挑选一个等待OS同步对象的线程,这可以确保它尽快重新开始运行。基础对象是一个简单的事件,一个非常便宜的操作系统对象。它还处理公平性,将等待的线程放入队列并以先入先出的顺序发布。我在this answer中记录了基础CLR代码。