2011-03-10 46 views
10

我已经陷入了一个混乱的混乱的多线程编程混乱,并希望有人能来和我巴掌一些理解。原子64位写入GCC

经过相当多的阅读后,我已经了解到,我应该能够在64位系统上自动设置一个64位整数值的值。

虽然我发现很多这种阅读困难,所以我想尝试做一个测试来验证这一点。所以我写了一个线程一个简单的程序,将设置一个变量为两个值中的一个:

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不是ab

大,看起来可能是原子写入......但后来,我改变了第一个线程设置ab直接,就像这样:

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

2:GCC Development list discussing that GCC doesn't guarantee it in the documentation, but the kernel and other programs rely on it

+0

编译时是否收到任何警告? – Nim 2011-03-10 11:00:35

+0

我不认为它是罪魁祸首,但默认情况下,文字具有int类型,所以您希望1844674407370955161ULL和1144644202170355111ULL为文字。 – etarion 2011-03-10 11:00:59

+0

Nim,编译时没有警告,并且设置了-Wall – Frederik 2011-03-10 11:06:11

回答

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 [email protected](%rip), %rax 
    movl $-1717986919, (%rax) 
    movl $429496729, 4(%rax) 
    jmp L5 
L3: 
    movq [email protected](%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位movl指令集和32位立即值。有一个指令movq可以将一个64位寄存器移动到内存(或内存到64位寄存器),但它似乎不能设置移动一个立即值到内存地址,所以编译器被强制要么使用一个临时寄存器,然后将该值移至内存,或者使用movl。您可以尝试通过使用临时变量强制它使用一个寄存器,但这可能不起作用。

参考文献:

+0

有趣!感谢您抽出宝贵的时间来解决这个问题! – Frederik 2011-03-10 11:25:10

+0

你使用什么编译器版本,平台和编译器选项?这造成了巨大的差异。如果以32位模式运行(OS为32位,或OS为64,但二进制为32),则写入将不是原子的,则64位写入将仅为原子对象,如果对象是8字节对齐,并且系统正在运行64位代码 – 2011-03-10 11:37:28

+1

GCC 4.2,MacOS X,CPU core i7,OS是64位,代码是针对x86_64架构编译的。 64位值的写入是原子的,但64位立即值不能在@AProgrammer指出的操作码中表示。因此,编译器必须先将立即值复制到寄存器中,然后再将其移到内存中,否则它必须非零原子地复制值的两个32位一半。 – 2011-03-10 11:41:08

3

英特尔CPU文档右对齐8个字节的读/写操作总是原子最近的硬件(即使在32位操作系统)。

你不告诉我们,你在32位系统上使用64位硬件吗?如果是这样,编译器最有可能将8字节的写入分割成两个4字节的写入

只需查看目标代码中的相关部分即可。

+0

嗨drhirsch,该系统是64位的Linux。 uname的输出是:Linux acorn 2.6.35-25-server#44 SMP Fri Feb 11 15:50:10 GMT 2011 x86_64 GNU/Linux – Frederik 2011-03-10 11:10:09

12

http://www.x86-64.org/documentation/assembly.html

内部指令的立即值保持32个比特。

编译器没有办法让原子地分配一个64位的常量,除了先填充一个寄存器然后将该寄存器移动到变量。这可能比直接分配给变量更昂贵,并且由于语言不需要原子性,所以不选择原子解决方案。

+1

+1链接,很棒的文档。谢谢。 – 2011-03-10 11:29:50

+0

另一个很棒的回复!谢谢! – Frederik 2011-03-10 11:30:33

+0

如果存在具有8字节值和8字节地址的mov立即指令,则所产生的指令大小将会很糟糕。可以看到他们为什么不想为此构建解码器! – 2011-03-10 17:34:58