2009-07-07 25 views
11

今天我遇到了這樣一個問題:一個彙編程序指令是否總是以原子方式執行?

你有一個代碼

static int counter = 0; 
void worker() { 
    for (int i = 1; i <= 10; i++) 
     counter++; 
} 

如果worker會從兩個不同的線程調用,將counter有什麼價值兩者都完成之後?

我知道實際上它可能是任何東西。但我的膽量內告訴我,那counter++將最有可能被翻譯成單一的彙編指令,如果兩個線程都執行相同的核心,counter將是20

但是,如果這些線程在不同的內核或運行處理器,他們的微代碼中是否會存在競爭條件?一個彙編指令總是可以被視爲原子操作嗎?

回答

17

專門針對x86,並且關於您的示例:counter++,可以通過多種方式進行編譯。最簡單的例子是:

inc counter 

這轉化爲以下微操作:

  • 負載counter到隱藏寄存器CPU
  • 增量寄存器
  • 存儲更新的寄存器在上counter

這是essentia LLY一樣:

mov eax, counter 
inc eax 
mov counter, eax 

請注意,如果其他一些代理更新的加載和存儲之間counter,它不會在商店後體現在counter。此代理可以是同一內核中的另一個線程,同一CPU中的另一個內核,同一系統中的另一個CPU,或者甚至是使用DMA(直接內存訪問)的某個外部代理。

如果要保證這inc是原子,使用lock前綴:

lock inc counter 

lock保證,沒有人可以更新加載和存儲之間counter


對於更復雜的指令,通常不能假設他們會以原子方式運行,除非他們支持lock前綴。

-3

我認爲你會得到一個訪問競​​賽條件。

如果你想確保遞增計數器的原子操作,那麼你需要使用++計數器。

+2

它與現代編譯器無關,它是相同的操作。 – vava 2009-07-07 09:01:33

3

不,你不能假設這一點。除非它在編譯器規範中明確說明。而且沒有人能保證單個彙編指令的確是原子的。實際上,每個彙編指令都被轉換爲微碼操作的數量 - 微操作。
此外,競爭條件問題與記憶模型(連貫性,順序性,釋放連貫性等)緊密結合,對於每個人來說,答案和結果可能不同。

6

並非總是 - 在某些體系結構中,一條彙編指令被翻譯成一條機器代碼指令,而另一條彙編指令則不轉換成一條機器代碼指令。

此外 - 你可以永遠假設你正在使用的程序語言編寫一個看似簡單的代碼行到一個彙編指令。而且,在某些體系結構中,不能假定一個機器代碼將以原子方式執行。

使用適當的同步技術,而不是依賴於你的編碼語言。

+0

解釋-1請 – 2009-07-07 08:56:07

+0

爲什麼downvote? – 2009-07-07 08:56:18

+3

一個彙編指令可以翻譯成許多微代碼指令 – 2009-07-07 08:59:40

5
  1. 在32位以內的整型變量遞增/遞減操作在一個單一的32位處理器不帶超線程技術是原子的。
  2. 在具有超線程技術的處理器上或在多處理器系統上,增量/減量操作不保證以原子方式執行。
2

另一個問題是,如果您沒有聲明變量爲volatile,那麼生成的代碼可能不會在每次循環迭代時更新內存,只有在循環結束時內存纔會更新。

4

無效Nathan的評論: 如果我正確記住我的英特爾x86彙編器,INC指令只適用於寄存器,並不直接工作的內存位置。

因此,計數器++不會是彙編器中的單個指令(只是忽略後增量部分)。它至少會有三條指令:將計數器變量加載到寄存器,遞增寄存器,將寄存器加載回計數器。這僅僅適用於x86架構。

總之,不要依賴它是原子的,除非它是由語言規範指定的,並且您使用的編譯器支持這些規範。

6

答案是:這要看!

這裏有一些困惑,彙編指令是什麼。通常,一條彙編指令被翻譯成一條機器指令。除了當你使用宏 - 但你應該知道這一點。

那就是說,問題歸結爲一個機器指令原子?

在過去的美好時光中,但是今天,使用複雜的CPU,長時間運行的指令,超線程......事實並非如此。一些CPU保證一些遞增/遞減指令是原子的。原因是,他們非常簡單的同步。

另外一些CPU命令不是那麼有問題。當你有一個簡單的獲取(處理器可以一次獲取的一塊數據)時,獲取本身當然是原子的,因爲根本沒有什麼可以分開的。但是,當你有未對齊的數據時,它會變得很複雜。

答案是:這取決於。仔細閱讀供應商的機器使用說明書。毫無疑問,這不是!

編輯: 哦,我現在看到它,你也要求++計數器。 「最有可能被翻譯的」聲明完全不可信。這在很大程度上也取決於編譯器當然!當編譯器進行不同的優化時,它會變得更加困難。

1

如果你想counter++真的是多線程原子,你可以使用System.Threading.Interlocked.Increment(counter),可能不是你的問題的實際答案,但(假設這是C#或其他.NET語言)。

查看其他答案的實際信息有很多不同的方式爲什麼/如何counter++不可能是原子。 ;-)

-1

在許多其他處理器上,內存系統和處理器之間的分離度較大。 (通常這些處理器可能是小端或大端的,取決於內存系統,如ARM和PowerPC),如果內存系統可以重新排序讀取和寫入,這也會對原子行爲產生影響。

爲此,有記憶障礙(http://en.wikipedia.org/wiki/Memory_barrier

因此,在短期,而原子指令有足夠的英特爾(有關鎖前綴),更必須在非英特爾來完成,因爲內存I/O可能不是相同的順序。

將英特爾的「無鎖」解決方案移植到其他架構時,這是一個已知問題。

(請注意,多處理器(未多核)在x86系統上也似乎需要記憶障礙,至少在64位模式。

0

在大多數情況下,沒有。事實上,在x86上,你可以執行指令

push [address] 

其中,在C,會是這樣的:

*stack-- = *address; 

這執行噸wo內存傳輸一條指令

這在1個時鐘週期內基本上是不可能的,不是最少的,因爲一個在一個週期內存儲器傳輸也是不可能的!

相關問題