2011-03-10 77 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