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

时间:2015-12-29 19:14:33

标签: c++ multithreading locking mutex

我想知道互斥锁(或其他锁定实现)如何实现锁定函数的wait functionallity。我的意思是,对于mutex.lock调用进行排队的cpu指令是仅在OS中实现的还是什么?

在我做的测试中,我认为这个等待功能只在OS层完成,并且创建了某种旋转,检查是否可以继续锁定,如果没有让线程进入休眠状态。是吗?

非常感谢。

2 个答案:

答案 0 :(得分:5)

它取决于平台。通常,如果达到固定的旋转限制,则存在旋转锁定部分,该旋转锁定部分回退到操作系统中的阻塞。

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

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

在伪代码中:

<强>锁定

  1. 检查内存位置以查看锁定是否已锁定。如果是,请转到步骤3.
  2. 尝试将内存位置从解锁状态原子切换为锁定状态。如果我们成功,请停止,我们持有锁。
  3. 增加旋转计数。如果我们没有旋转太多次,请转到步骤1.
  4. 以原子方式增加等待此锁定的线程数。
  5. 尝试将内存位置从解锁状态原子切换为锁定状态。如果我们成功,减少等待线程的数量并停止,我们持有锁。
  6. 有条件地阻止操作系统。
  7. 转到第5步。
  8. <强>解锁

    1. 以原子方式将保持锁定状态的内存位置设置为已解锁。
    2. 如果在OS中等待此锁定的线程数大于零,请告诉操作系统取消阻止等待此锁定的所有线程。
    3. 请注意,操作系统必须实现某种机制,以避免在线程设置阻塞之前发生取消阻止在OS中等待的任何线程的请求的竞争。该方法因操作系统而异。例如,Linux有一种称为“futex”的东西,它本质上是一种以原子方式实现锁定伪代码的第4步,第5步和第6步的方法。

      警告:如果您尝试在代码中实现此算法,请了解您可能会生成一个与正确实现不同的玩具。您需要深入的,平台特定的知识,以避免令人讨厌的性能吸引陷阱。例如,很容易对自旋锁进行编码,以便从使用超线程共享CPU中的物理内核的另一个线程中窃取核心执行资源。并且很容易编码成功的交换,以便CPU的分支预测预测它会失败,并且当你获得锁定时你会受到可怕的分支错误预测惩罚。

答案 1 :(得分:2)

它将here解释为:

  

等待怎么样?

     

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

     

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

     

此时各种操作系统大相径庭,并且随着时间的推移可能会发生变化。在linux下有一个系统调用futex,它提供类似语义的互斥。它经过专门设计,可以在用户空间中完全解决非争用情况。然后将争用案例委托给操作系统以便以安全的方式处理,尽管成本更高。然后等待作为OS进程调度程序的一部分进行处理。