互斥锁如何在低级别等待解锁

How do mutex lock waits for unlock at low level?

本文关键字:等待 解锁      更新时间:2023-10-16

我想知道互斥锁(或其他锁定实现)是如何实现锁函数的等待功能的。我的意思是,这是一条对互斥锁调用进行排队的cpu指令,是仅在操作系统中实现的还是什么?

在我所做的测试中,我认为这种等待功能只在操作系统层中完成,并且是创建某种旋转的,检查锁是否可以继续,如果不让线程进入睡眠状态。是这样吗?

非常感谢。

它依赖于平台。通常,如果达到固定的旋转限制,操作系统中会有一个旋转锁定部分返回到阻塞状态。

自旋锁通常是通过在互斥锁解锁时读取包含特定值的内存地址来实现的。如果它被视为未锁定,则尝试将该值从未锁定值原子性地更改为锁定值。如果原子交换成功,互斥锁就会被锁定。通常会计算旋转次数,如果达到限制,我们会在操作系统中切换到阻塞。

操作系统中的块通常以大致相同的方式实现,只是线程将自己添加到等待锁定的事物列表中,而不是休眠。当线程释放锁时,它会检查操作系统中是否有任何东西在等待,如果有,则解除锁定。这会导致操作系统调度该线程。然后,它通常会尝试执行与自旋锁尝试的原子交换相同的原子交换,如果失败,则会在操作系统中再次阻塞。

在伪代码中:

锁定

  1. 检查内存位置,查看锁是否已锁定。如果是,请转至步骤3
  2. 尝试将内存位置从解锁自动切换到锁定。如果我们成功了,停下来,我们就锁住了
  3. 增加旋转计数。如果我们没有旋转太多次,请转到步骤1
  4. 原子递增等待此锁的线程数
  5. 尝试将内存位置从解锁自动切换到锁定。如果我们成功了,减少等待线程的数量并停止,我们就锁定了
  6. 在操作系统中有条件地阻止
  7. 转到步骤5

解锁

  1. 原子化地将保持锁定状态的内存位置设置为unlock
  2. 如果操作系统中等待此锁的线程数大于零,请告诉操作系统取消阻止任何等待此锁

请注意,操作系统必须实现一些机制来避免竞争,即在线程设法阻止之前,请求取消阻止在操作系统中等待的任何线程。方法因操作系统而异。例如,Linux有一种叫做"futex"的东西,它本质上是一种实现原子锁定伪代码的步骤4、5和6的方法。

注意:如果您试图在代码中实现此算法,请理解您可能会产生一个性能不如正确实现的玩具。你需要深入的、特定于平台的知识,以避免陷入糟糕的性能陷阱。例如,很容易对spinlock进行编码,这样它就可以从另一个使用超线程共享CPU中物理核心的线程中窃取核心执行资源。而且很容易对成功的交换进行编码,这样CPU的分支预测就会预测它将失败,并且在获得锁时会受到可怕的分支预测失误的惩罚。

这里解释为:

等待怎么样

现在是棘手的部分。好吧,只有在某种程度上它是棘手的,在另一方面它是简单的。上面的测试和设置机制不支持线程等待值(除了CPU密集型的旋转锁)。CPU并不真正理解高级线程和进程,因此无法实现等待。操作系统必须提供等待功能。

为了让CPU正确等待,调用方需要进行系统调用。它是唯一可以同步各种线程并提供等待功能的东西。因此,如果我们必须等待一个互斥体,或者释放一个等待的互斥体,我们别无选择,只能调用操作系统。大多数操作系统都内置了互斥原语。在某些情况下,它们提供了成熟的互斥。那么,如果系统调用确实提供了一个完整的互斥体,我们为什么要在用户空间中进行任何类型的测试和设置呢?答案是系统调用有相当大的开销,应该尽可能避免。

各种操作系统在这一点上有很大的差异,并且可能会随着时间的推移而变化。在linux下,有一个系统调用futex,它提供了类似互斥的语义。它是专门设计的,以便在用户空间中完全解决非争用情况。然后,争用案例被委托给操作系统以安全的方式处理,尽管成本要高得多。然后,等待作为操作系统进程调度程序的一部分进行处理。