C++ 什么是;锁;x86汇编中的指令意味着什么?

C++ 什么是;锁;x86汇编中的指令意味着什么?,c++,qt,assembly,x86,C++,Qt,Assembly,X86,我在Qt的源代码中看到一些x86程序集: q_atomic_increment: movl 4(%esp), %ecx lock incl (%ecx) mov $0,%eax setne %al ret .align 4,0x90 .type q_atomic_increment,@function .size q_atomic_increment,.-q_atomic_increment 通过谷歌搜索,我知道l

我在Qt的源代码中看到一些x86程序集:

q_atomic_increment:
    movl 4(%esp), %ecx
    lock 
    incl (%ecx)
    mov $0,%eax
    setne %al
    ret

    .align 4,0x90
    .type q_atomic_increment,@function
    .size   q_atomic_increment,.-q_atomic_increment
  • 通过谷歌搜索,我知道
    lock
    指令将导致CPU锁定总线,但我不知道CPU何时释放总线

  • 关于上面的全部代码,我不明白这段代码如何实现
    Add

  • 从谷歌,我知道锁指令将导致cpu锁定总线,但我 不知道cpu什么时候释放总线

    LOCK
    是一个指令前缀,因此它只适用于以下指令,这里的源代码不太清楚,但真正的指令是
    LOCK INC
    。因此,总线在增量时锁定,然后解锁

    关于上面的全部代码,我不明白这些代码是怎么写的 实现了Add

    它们不实现加法,而是实现一个增量,如果旧值为0,则还提供一个返回指示。添加将使用
    LOCK XADD
    (但是,windows InterlockedIncrement/Decreation也使用
    LOCK XADD
    实现)

  • LOCK
    不是指令本身:它是一个指令前缀,适用于以下指令。该指令必须是在内存上执行读-修改-写操作的指令(
    INC
    XCHG
    CMPXCHG
    等)——在这种情况下,是
    incl(%ecx)
    指令
    INC
    ecx
    寄存器中保存的地址处重新输入
    l
    ong字

    LOCK
    前缀确保CPU在操作期间对适当的缓存线拥有独占所有权,并提供某些额外的排序保证。这可以通过断言总线锁来实现,但CPU将尽可能避免这种情况。如果总线被锁定,则仅在锁定指令的持续时间内

  • 此代码将要从堆栈中递增的变量的地址复制到
    ecx
    寄存器中,然后执行
    lock incl(%ecx)
    以原子方式递增该变量1。接下来的两条指令将
    eax
    寄存器(保存函数返回值)设置为0(如果变量的新值为0),否则设置为1。该操作是一个增量操作,而不是一个添加操作(因此得名)


  • 您可能无法理解的是,增加值所需的微码要求我们首先读入旧值

    Lock关键字强制实际发生的多个微指令看起来是原子操作的

    如果有两个线程分别尝试递增相同的变量,并且它们都同时读取相同的原始值,那么它们都递增到相同的值,并且都写出相同的值

    与通常期望的变量递增两次不同,最终的结果是变量递增一次


    锁定关键字防止发生这种情况。

    < P> <强>最小可运行C++线程+锁内联汇编示例<强> < /P> main.cpp

    #include <atomic>
    #include <cassert>
    #include <iostream>
    #include <thread>
    #include <vector>
    
    std::atomic_ulong my_atomic_ulong(0);
    unsigned long my_non_atomic_ulong = 0;
    unsigned long my_arch_atomic_ulong = 0;
    unsigned long my_arch_non_atomic_ulong = 0;
    size_t niters;
    
    void threadMain() {
        for (size_t i = 0; i < niters; ++i) {
            my_atomic_ulong++;
            my_non_atomic_ulong++;
            __asm__ __volatile__ (
                "incq %0;"
                : "+m" (my_arch_non_atomic_ulong)
                :
                :
            );
            __asm__ __volatile__ (
                "lock;"
                "incq %0;"
                : "+m" (my_arch_atomic_ulong)
                :
                :
            );
        }
    }
    
    int main(int argc, char **argv) {
        size_t nthreads;
        if (argc > 1) {
            nthreads = std::stoull(argv[1], NULL, 0);
        } else {
            nthreads = 2;
        }
        if (argc > 2) {
            niters = std::stoull(argv[2], NULL, 0);
        } else {
            niters = 10000;
        }
        std::vector<std::thread> threads(nthreads);
        for (size_t i = 0; i < nthreads; ++i)
            threads[i] = std::thread(threadMain);
        for (size_t i = 0; i < nthreads; ++i)
            threads[i].join();
        assert(my_atomic_ulong.load() == nthreads * niters);
        assert(my_atomic_ulong == my_atomic_ulong.load());
        std::cout << "my_non_atomic_ulong " << my_non_atomic_ulong << std::endl;
        assert(my_arch_atomic_ulong == nthreads * niters);
        std::cout << "my_arch_non_atomic_ulong " << my_arch_non_atomic_ulong << std::endl;
    }
    
    可能的产出:

    my_non_atomic_ulong 15264
    my_arch_non_atomic_ulong 15267
    
    由此我们可以看出,锁前缀使加法原子化:没有它,我们在许多加法上都有竞争条件,并且最后的总计数小于同步的20000

    锁定前缀用于实现:

    • C++11
      std::atomic
    • C11
      atomic\u int
    另见:


    在Ubuntu19.04 amd64中测试。

    请参阅相关内容:我的回答解释了x86上的原子性,以及
    锁前缀的确切作用,以及没有它会发生什么。谢谢!然后哪个寄存器存储函数(q_原子_增量)的返回值?返回值存储在%eaxSo中,代码:“return q_原子_增量(&_q_值)!=0”用于测试%eax是否不等于零?@gemfield:its zero'd,然后使用
    INC
    中的条件标志通过
    SETNE
    设置LSB。是在%eax中返回的旧值是0还是不是0(如答案当前所述),还是新值?因此指令“mov$0,%eax”似乎是多余的?@gemfield:No,
    mov
    将所有
    eax
    设置为零
    SETNE
    仅更改低位字节。如果没有
    MOV
    EAX
    的3个高字节将包含以前操作的随机剩余值,因此返回值将是不正确的。在俄罗斯的一本书“Assembler for DOS,WindowsêLinux,2000.Sergei Zukkov”中,作者提到了关于此前缀的以下内容:“在命令执行的所有时间内,如果有这样一个前缀,数据总线将被挂起,如果系统有不同的处理器,它将无法访问内存,直到带有前缀锁的命令结束。即使未指定锁前缀,XCHG命令也始终自动使用内存访问锁执行。此前缀只能与ADD、ADC和、BTC、BTR、BTS、CMPXCHG、DEC、INC、NEG、NOT或、SBB、SUB、XOR、XADD和XCHG命令一起使用。“@bruziuz:现代CPU的效率要高得多:如果
    lock
    ed指令的数据没有穿过缓存线,CPU内核就可以在内部锁定该缓存线,而不是阻止所有其他内核的所有加载/存储。另请参阅我在上的回答,以了解有关如何使其对使用MESI缓存一致性协议的可能观察者显示原子的更多详细信息。非常感谢!酷!:)使用
    -O0
    ,并用一个完整的屏障(
    lock inc
    )来限制非原子增量有什么意义?证明在最好的情况下它仍然是收支平衡的?如果您让non-locked
    inc
    从存储缓冲区向前移动,您会看到更多的计数丢失。@PeterCordes
    -O0
    :没有花太多心思,默认情况下是为了更好地调试而做的,不过我最近注意到,这样做确实会让您更容易看到我这样简单的行为
    my_non_atomic_ulong 15264
    my_arch_non_atomic_ulong 15267