我遇到了一个(基本)自旋锁互斥锁的问题,它似乎无法按预期工作。
4 个线程正在递增一个受此互斥体保护的非原子计数器。 结果与预期结果不匹配,这使得互斥锁似乎被破坏了。
示例输出:
result: 2554230
expected: 10000000
在我的环境中,它发生在以下条件下:
flag
是std::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/