资源上的争用——不总是使用锁

本文关键字:争用 资源 | 更新日期: 2023-09-27 18:04:06

我看了jeffery richter 视频(点击查看确切线条),他说:

使用Monitor总是更好。输入并监控。锁在事件等待句柄或信号量等,因为它们(monitor.X) 使用内核对象,但是它们仅在争用时使用。和如果没有争用,它们不使用这些对象。

我可能在这里遗漏了一些东西,但是当我这样做的时候:

lock(myObj)
{
 ...
}

I 假定可能有多个线程想要进入临界区。
那么,根据上面的信息,如果没有争用,锁就不会被使用?(如果另一个线程要在1毫秒后进入该怎么办?)

资源上的争用——不总是使用锁

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

Monitor.Enter()方法通过首先检查锁是否已经由同一线程拥有来避免输入操作系统代码。这使得它可重入,如果是这种情况,它只是增加锁计数。接下来,它尝试使用interlocking . compareexchange()的等效方法来获取锁,这在任何处理器上都是一个非常便宜的原语。值得注意的是,它的x86版本实际上根本没有使用总线锁,您可以在这个答案中看到它的代码。

如果这不起作用,那么操作系统需要参与进来,因为现在重要的是,它可以进行线程上下文切换,以便在锁被释放时唤醒线程。Windows非常喜欢选择正在等待操作系统同步对象的线程,这可以确保它尽可能快地重新开始运行。底层对象是一个简单的事件,一个非常便宜的OS对象。它还考虑了公平性,等待的线程被放入队列并按先入先出的顺序释放。我在这个回答中记录了底层CLR代码。

那么,根据上面的信息,如果没有争用,锁就不会被使用?(如果另一个线程要在1毫秒后进入该怎么办?)

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

操作看起来是这样的:

锁:

  1. 尝试自动将用户空间锁变量从unlock设置为lock。

  2. 增加用户空间竞争计数

  3. 设置内核空间锁为locked

  4. 尝试自动将用户空间锁变量从unlock设置为lock。

解锁:

  1. 自动设置用户空间锁变量从lock到unlock

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

  3. 设置内核锁为未锁定

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

lock语句只是使用Monitor.EnterMonitor.Exit作为实现的语法糖。

Monitor函数本身是使用条件变量实现的。它们的实现意味着它们不需要分配内核对象,除非锁确实存在争用。当这种情况发生时,他们必须继续分配一个内核对象。

即使存在锁争用,它们也不会立即分配内核对象。相反,它们"旋转"(只是在一个紧密的循环中停留一小会儿),希望锁可以释放。只有当它不自由时,它才会继续分配/使用内核对象。

注意一些新的同步类,比如ManualResetEventSlim也采用了这种方法。(一般来说,任何以"Slim"结尾的同步类都是这样做的。)

也看这个线程。

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