c++ - 原子 bool 无法保护非原子计数器

标签 c++ multithreading c++11 mutex atomic

我遇到了一个(基本)自旋锁互斥锁的问题,它似乎无法按预期工作。

4 个线程正在递增一个受此互斥体保护的非原子计数器。 结果与预期结果不匹配,这使得互斥锁似乎被破坏了。

示例输出:

  result: 2554230
expected: 10000000

在我的环境中,它发生在以下条件下:

  • flagstd::atomic<bool> , 任何其他内容,例如 std::atomic<int>std::atomic_flag (与 test_and_set )工作正常。

  • 使用 gcc 6.3.1 和 -O3 在 X86_64 上编译标记

我的问题是,什么可以解释这种行为?

#include <iostream>
#include <vector>
#include <atomic>
#include <thread>
#include <mutex>
#include <assert.h>

class my_mutex {
    std::atomic<bool> flag{false};

public:
    void lock()
    {
        while (flag.exchange(true, std::memory_order_acquire));
    }

    void unlock()
    {
        flag.store(false, std::memory_order_release);
    }
};


my_mutex mut;
static int counter = 0;


void increment(int cycles)
{
    for (int i=0; i < cycles; ++i)
    {
        std::lock_guard<my_mutex> lck(mut);

        ++counter;
    }
}

int main()
{
    std::vector<std::thread> vec;
    const int n_thr = 4;
    const int n_cycles = 2500000;

    for (int i = 0; i < n_thr; ++i)
        vec.emplace_back(increment, n_cycles);

    for(auto &t : vec)
        t.join();

    std::cout << "  result: " << counter << std::endl;
    std::cout << "expected: " << n_cycles * n_thr << std::endl;
}

编辑

根据 Voo 的请求,这里是 increment() 的程序集输出..

$ g++ -O3 increment.cpp
$ gdb a.out
Reading symbols from a.out...done.
(gdb) disassemble increment
Dump of assembler code for function increment(int):
   0x0000000000401020 <+0>:     mov    0x20122a(%rip),%ecx        # 0x602250 <_ZL7counter>
   0x0000000000401026 <+6>:     test   %edi,%edi
   0x0000000000401028 <+8>:     mov    $0x1,%edx
   0x000000000040102d <+13>:    lea    (%rdi,%rcx,1),%esi
   0x0000000000401030 <+16>:    jle    0x401058 <increment(int)+56>
   0x0000000000401032 <+18>:    nopw   0x0(%rax,%rax,1)
   0x0000000000401038 <+24>:    mov    %edx,%eax
   0x000000000040103a <+26>:    xchg   %al,0x20120c(%rip)        # 0x60224c <mut>
   0x0000000000401040 <+32>:    test   %al,%al
   0x0000000000401042 <+34>:    jne    0x401038 <increment(int)+24>
   0x0000000000401044 <+36>:    add    $0x1,%ecx
   0x0000000000401047 <+39>:    cmp    %ecx,%esi
   0x0000000000401049 <+41>:    mov    %ecx,0x201201(%rip)        # 0x602250 <_ZL7counter>
   0x000000000040104f <+47>:    movb   $0x0,0x2011f6(%rip)        # 0x60224c <mut>
   0x0000000000401056 <+54>:    jne    0x401038 <increment(int)+24>
   0x0000000000401058 <+56>:    repz retq
End of assembler dump.

最佳答案

您的代码是正确的。这是一个 bug 80004 - [6 回归] 使用 std::memory_order_acquire 将非原子负载移至原子负载之前

关于c++ - 原子 bool 无法保护非原子计数器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42147192/

相关文章:

c++ - 是否可以将项目移出std::set?

c++ - 如何理解 "std::future shared state"?

c++ - 在 C++ 中编写自定义 for 循环

c++ - 为什么我必须在模板中的每个函数上方声明模板?

c++ - 如何删除小部件

c - 在这种情况下我真的需要互斥锁吗?

c++ - 复制构造函数和复制赋值运算符应该有相同的语句吗?

c# - .net 中 Thread 类和 ProcessThread 类的区别?

java - Swingworker.done() 在完成后卡住 GUI 几秒钟

c++ - 如何从 std::function 推断返回值类型?