【问题标题】:How does "lock cmpxchg" work in assembly?“lock cmpxchg”如何在汇编中工作?
【发布时间】:2020-03-20 02:16:45
【问题描述】:

我遇到了这个旧的(4.8.3 之前的 GCC -- 错误 60272)错误报告 https://gcc.gnu.org/ml/gcc-bugs/2014-02/msg01951.html。现在已修复。但我对此有一个疑问。我编译了以下代码sn -p

#include <atomic>
struct Node { Node* next; };
void Push(std::atomic<Node*>& head, Node* node)
{
    node->next = head.load();
    while(!head.compare_exchange_weak(node->next, node))
        ;
}

void Pop(std::atomic<Node*>& head){
    for(;;){
        Node* value=head.exchange(nullptr);
        if(value){
            delete value;
            break;
        }
    }
}

与:

g++ -S -std=c++11 -pthread -O3 test.cc -o test.S

生成的程序集有以下内容(我只放了相关部分):

.....
  4 .L4:
  5   lock cmpxchgq %rsi, (%rdi)
  6   jne .L6
  7   rep ret
  8   .p2align 4,,10
  9   .p2align 3
 10 .L6:
 11   movq  %rax, (%rsi)
 12   jmp .L4
.....

这是我的问题。假设这段代码与 2 个线程同时运行。对于 T1,第 5 行被执行,然后 T1 被中断,T2 做了一些可能使队列弹出完成的事情。当操作系统重新安排 T1 时,它将从第 6 行恢复,在执行 jne 之前有人应该**重新评估**条件。但是如果不重新评估它,那么这可能会导致内存损坏。我的想法是否正确?

【问题讨论】:

  • 比较和交换操作旨在始终保持队列不受污染。如果成功,则队列正确更新,如果没有,则可以在下一次迭代中构建新的正确队列。在第 6 行,队列已经正确更新,测试是在 CAS 失败时继续循环。
  • 如果您想知道 lock cmpxchg 如何实现原子性,请参阅 Can num++ be atomic for 'int num'? 了解有关 lock 前缀如何工作的更多信息。

标签: c++ gcc assembly x86-64 lockless


【解决方案1】:

cmpxchg 指令只有在匹配eax 时才会设置dst。否则它将跳转到L6 更新eax 并重新启动循环。 lock 前缀允许对当前指令的任何内存操作数进行独占访问。换句话说,这个atomically 推送节点直到它成功。这个错误是因为他们最初没有检查结果。

【讨论】:

  • 这不是自旋锁;它是无锁的。 (它可能需要重试,所以它不是免等待)
  • 它处于繁忙的等待循环中,直到原子操作成功,这不符合自旋锁的条件吗?
  • 不,因为它没有等待另一个线程(如果你有一个真正的锁,它可能已经进入睡眠状态并持有一个锁)。在任何点,N 个线程可以尝试 CAS,其中一个会成功。这样就有了前进的保证。 en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom。 CAS 重试循环是无锁但非无等待算法构建块的最常见示例。
  • 是的,但这不是一个忙碌的等待。它不会在成功之前等待某个条件,它只是尝试一个可以随时成功的操作,只有在它实际与另一个线程竞争时才会失败。
  • .L6 处的指令不会更新 EAX,.L4 处的指令会更新 EAX。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-27
  • 1970-01-01
  • 1970-01-01
  • 2013-02-12
  • 2014-01-29
  • 2015-06-14
相关资源
最近更新 更多