【问题标题】:How is spin lock implemented under the hood?自旋锁是如何在底层实现的?
【发布时间】:2010-05-23 13:40:22
【问题描述】:

这是一个可以被持有的锁 一次只有一个执行线程 时间。尝试获取锁 通过另一个执行线程使 后一个循环直到锁定 发布。

当两个线程试图同时获取锁时如何处理?

我认为这个问题也适用于其他各种互斥锁实现。

【问题讨论】:

    标签: language-agnostic concurrency mutex spinlock


    【解决方案1】:

    如上一张海报所示,每一种现代机器类型都有一种特殊的指令类别,称为“原子”,它们的运行方式与上一张海报所示的一样……它们至少针对指定的内存位置序列化执行。

    在 x86 上,有一个 LOCK 汇编器前缀,指示机器应该以原子方式处理下一条指令。当遇到指令时,x86 上会发生几件事情。

    1. 挂起的读取预取被取消(这意味着 CPU 不会向程序提供可能在原子范围内变得陈旧的数据)。
    2. 对内存的挂起写入被刷新。
    3. 该操作以原子方式执行、保证并针对其他 CPU 进行序列化。在这种情况下,“序列化”意味着“它们一次发生一个”。原子的意思是“这条指令的所有部分都在没有其他任何干预的情况下发生”。

    对于 x86,有两个常用的指令用于实现锁。

    1. CMPXCHG。有条件的交换。伪代码:
    uint32 cmpxchg(uint32 *memory_location,uint32 old_value,uint32 new_value){ 原子地{ if (*memory_location == old_value) *memory_location = new_value; 返回旧值; } }
    1. XCHG。伪代码:
    uint32 xchg(uint32 *memory_location, uint32 new_value) { 原子地{ uint32 old_value = *memory_location; *memory_location = new_value; 返回 *old_value; } }

    所以,你可以像这样实现一个锁:

    uint32 mylock = 0; 而 (cmpxchg(&mylock, 0, 1) != 0) ;

    我们自旋,等待锁,因此,自旋锁。

    现在,解锁的指令不会表现出这些好的行为。根据您使用的机器,使用未锁定的指令,可以观察到各种违反一致性的行为。例如,即使在具有非常友好的内存一致性模型的 x86 上,也可以观察到以下情况:

    线程 1 线程 2 移动 [w], 0 移动 [x], 0 mov [w], 1 mov [x], 2 mov eax, w mov eax, x mov [y], eax mov [z], eax

    在这个程序结束时,y 和 z 的值都可以是 0!

    无论如何,最后一点:x86 上的 LOCK 可以应用于 ADD、OR 和 AND,以便为指令获得一致的原子读-修改-写语义。例如,这对于设置标志变量并确保它们不会丢失很重要。没有它,你就会遇到这个问题:

    线程 1 线程 2 与 [x]、0x1 与 [x]、0x2

    在这个程序结束时,x 的可能值是 1、2 和 0x1|0x2 (3)。为了获得正确的程序,您需要:

    线程 1 线程 2 锁定与 [x]、0x1 锁定与 [x]、0x2

    希望这会有所帮助。

    【讨论】:

    • 我认为在您对 cmpxchg() 的定义中,您可能希望返回内存位置的实际值。来自en.wikipedia.org/wiki/Compare-and-swap,“操作的结果必须表明它是否执行了替换;这可以通过返回从内存位置读取的值(而不是写入它的值)来完成。”
    【解决方案2】:

    取决于处理器和线程实现。大多数处理器都有可以原子执行的指令,在这些指令上您可以构建诸如自旋锁之类的东西。例如 IA-32 有一个执行原子交换的xchg 指令。然后,您可以实现一个简单的自旋锁,例如:

      eax = 1;
      while( xchg(eax, lock_address) != 0 );
      // now I have the lock
      ... code ...
      *lock_address = 0; // release the lock
    

    【讨论】:

    • 同样,这取决于处理器架构。 xchg 的实现是一个微码/硬件问题。最终,实际上没有任何事情发生在完全相同的时间,即使有多个处理器/内核它们共享一条总线和一个时钟。应该可以找到针对特定处理器的这些指令之一的控制路径(SPARC 可能是最简单的),但不太可能有一个普遍适用的特定答案。
    • 所以锁定最终是基于没有任何事情同时发生的事实来实现的?
    • 说“实际上没有任何事情发生在同一时间”在某种意义上对我来说是夸大了。相反,有些事情不能与它们自己同时发生。一个例子可能是,假设您有一台具有 32 位内存的机器。两个处理器不能同时翻转位 0,只有一条逻辑线通向该位(触发器或其他)。在这些属性之上构建了诸如 xchg 之类的东西,并在诸如 xchg 之类的东西之上构建了自旋锁、互斥体等。
    • @httpinterpret (via fante) 为了说明清楚,需要在“共享”内存中定义“锁”。在多处理器场景中,硬件强制每个内核对这个“共享”内存(锁?)进行“序列化”访问。因此,正如您所见,“共享”内存上永远不会有 2 个或更多并发执行“xchg”指令。因此,Logan Capaldo 提出的“朴素”自旋锁(如上所示)应该可以完美运行。无论如何,我不是专家。任何建议将不胜感激。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-15
    • 2020-02-20
    • 2018-02-08
    • 2018-11-13
    • 2014-12-29
    • 1970-01-01
    相关资源
    最近更新 更多