我对多线程编程感到困惑,希望有人可以来帮我理解。
在做了大量的阅读之后,我已经明白我应该能够在64位系统 1 上原子地设置64位int的值。
我发现很多这种阅读困难,所以我想我会尝试做一个测试来验证这一点。所以我用一个线程编写了一个简单的程序,它将一个变量设置为两个值中的一个:
bool switcher = false;
while(true)
{
if (switcher)
foo = a;
else
foo = b;
switcher = !switcher;
}
另一个线程会检查foo
:
while (true)
{
__uint64_t blah = foo;
if ((blah != a) && (blah != b))
{
cout << "Not atomic! " << blah << endl;
}
}
我设置a = 1844674407370955161;
和b = 1144644202170355111;
。我运行此程序并且没有输出警告我blah
不是a
或b
。
很好,看起来它可能是一个原子写...但是,然后,我改变了第一个线程直接设置a
和b
,如下所示:
bool switcher = false;
while(true)
{
if (switcher)
foo = 1844674407370955161;
else
foo = 1144644202170355111;
switcher = !switcher;
}
我重新跑,突然:
Not atomic! 1144644203261303193
Not atomic! 1844674406280007079
Not atomic! 1144644203261303193
Not atomic! 1844674406280007079
改变了什么?无论哪种方式,我都要为foo
分配一个大数字 - 编译器是否会以不同的方式处理常数,或者我误解了所有内容?
谢谢!
<小时/> 1:Intel CPU documentation, section 8.1, Guaranteed Atomic Operations
答案 0 :(得分:12)
反汇编循环,我使用gcc
得到以下代码:
.globl _switcher
_switcher:
LFB2:
pushq %rbp
LCFI0:
movq %rsp, %rbp
LCFI1:
movl $0, -4(%rbp)
L2:
cmpl $0, -4(%rbp)
je L3
movq _foo@GOTPCREL(%rip), %rax
movl $-1717986919, (%rax)
movl $429496729, 4(%rax)
jmp L5
L3:
movq _foo@GOTPCREL(%rip), %rax
movl $1486032295, (%rax)
movl $266508246, 4(%rax)
L5:
cmpl $0, -4(%rbp)
sete %al
movzbl %al, %eax
movl %eax, -4(%rbp)
jmp L2
LFE2:
因此看起来gcc
确实用于具有32位立即值的32位movl
指令。有一条指令movq
可以将64位寄存器移动到存储器(或存储器移动到64位寄存器),但它似乎无法将立即值移动到存储器地址,所以编译器被强制要么使用临时寄存器,然后将值移动到内存,要么使用movl
。您可以尝试使用临时变量强制它使用寄存器,但这可能不起作用。
参考文献:
答案 1 :(得分:12)
http://www.x86-64.org/documentation/assembly.html
指令内的立即值仍为32位。
编译器无法原子地分配64位常量,除非首先填充寄存器然后将该寄存器移动到变量。这可能比直接分配给变量更昂贵,并且由于语言不需要原子性,因此不选择原子解决方案。
答案 2 :(得分:3)
Intel CPU文档是正确的,对齐的8字节读/写在最近的硬件上总是原子的(即使在32位操作系统上)。
您没有告诉我们,您是否在32位系统上使用64位硬件?如果是这样,8字节写入很可能被编译器分成两个4字节写入。
只需查看目标代码中的相关部分即可。