2011-04-24 172 views
3

以下是比較原子動作嗎?也就是說,它可以減少到單個CPU指令嗎?是一個比較原子操作嗎?

char flag = 2; 

for(;;) 
{ 
    if (!flag) // <-- this 
     break; 

    // sleep 
} 

下面是我在做什麼:

int main() 
{ 
    sf::Mutex Mutex; 
    char flag = 2; 

    coordinatorFunction(flag); 

    for(;;) 
    { 
     if (!flag) 
      break; 

     // sleep 
    } 
} 

void workerFunction(void* a) 
{ 
    char* p = static_cast<char*>(a); 

    // work 

    GlobalMutex.Lock(); 
    --*p; 
    GlobalMutex.Unlock(); 
} 

void coordinatorFunction(char& refFlag) 
{ 
    sf::Thread worker1(&workerFunction, &refFlag); 
    sf::Thread worker2(&workerFunction, &refFlag); 

    worker1.Launch(); 
    worker2.Launch(); 
} 
+0

我認爲在執行比較之前,它總是必須讀入寄存器,因爲您已將指針傳遞給它。所以不,它不會是原子的。這就是我的想法,我不知道。我相信有辦法做原子比較和寫,雖然(不是在我猜的C++語言本身).. – falstro 2011-04-24 19:55:03

+2

它不遵循,如果東西可以減少到一個單一的CPU指令,那麼它是原子。首先並非所有架構上的所有CPU指令都是原子的,其次,僅僅是因爲某些東西可以簡化爲一個原子的指令並不意味着它會是原子的。 – 2011-04-24 20:00:37

回答

7

這是錯誤的做法。

您的主線程正在儘可能快地燒燬CPU週期,除了等待flag達到零之外什麼都不做。每次嘗試時都會失敗,除了最後一次。不要這樣做,而是使用線程對象最有可能使主線程自行掛起的「加入」工具,直到所有工作人員完成。

這樣,不是巧合,你不會在乎測試是否是原子的,因爲你根本不需要它。

+0

該死的。我應該想到這一點。謝謝。 – Truncheon 2011-04-24 20:02:05

+0

幹得好,很好的答案。 +1 – Mehrdad 2011-04-24 20:03:37

+0

我相信原來的代碼沒有忙着等待:) – seand 2011-04-24 20:04:02

1

沒有,C++不保證任何操作是原子。你問題中的代碼很可能被編譯成一個從內存加載到一個寄存器中的代碼,它本身可能需要多個指令,然後進行測試。

+0

那麼我需要更改代碼以使其線程安全,還是已經? – Truncheon 2011-04-24 19:59:06

+0

@Truncheon術語「線程安全」是相當無意義的 - 線程安全性完全取決於上下文。但總的來說,從多個線程訪問的任何資源(變量,無論)都必須受到某種鎖定的保護。留下他們自己的設備,編譯器永遠不會生成「線程安全」的代碼。 – 2011-04-24 20:04:19

2

C++操作沒有保證是原子操作。

2

在C++中沒有保證是原子的。

1

比較不是原子的,因爲它使得幾個機器語言指令來完成它(從內存加載到一個寄存器等)。由於內存模型的靈活性和緩存,執行測試的線程可能不會「看到」馬上就有另一個線程的結果。

您可能會安全地做一個簡單測試變量是否標記爲易失性,但這將是平臺特定的。正如其他人指出的那樣,C++本身並不保證。

1

甲比較涉及讀數二者片數據,以及執行實際的比較。 數據可以在讀取和比較指令之間改變,所以它不是原子的。

但是,因爲您正在比較相等性,所以_InterlockedCompareExchange固有內存(適用於x86中的lock cmp xchg指令)可能會執行您所需的操作,但它會涉及替換數據。

+0

但是,在許多平臺上,如果比較結果爲零,則有專門的操作碼可以執行該操作。這意味着如果該值已經在寄存器中,它確實可以以原子方式完成。 – Kylotan 2011-04-24 19:58:30

+0

@Kylotan:我認爲它不重要,你仍然需要將它讀入一個寄存器,然後*然後*比較。問題不是比較,而是中間狀態。但是如果你有指向內存的指針(而不是寄存器中的值),那麼是的,這是正確的;我已經更新了我的答案。 – Mehrdad 2011-04-24 19:59:34

+0

*如果該值已經在寄存器中,但您將如何保證? – seand 2011-04-24 20:01:11

2

不可以。據我所知,C++並不保證什麼是原子,什麼不是 - 這是特定於平臺的。即使您的平臺確保可以自動進行比較,也不能保證您的C++編譯器會選擇該指令。

然而,在實踐中,簡單的值類型比如char,int,float等的比較可能是原子的。無論是在編譯器級別還是在處理器級別,您仍然需要了解可能的指令重新排序。在這種情況下,這可能並不重要,但在一般情況下,它可以並且確實如此。您還需要了解,即使比較結果是比較後分支也不是原子的,因此如果您嘗試使用標誌來規範該訪問,則2個線程都可以進入相同的代碼塊。

如果您需要適當的保證,Windows上有各種Interlocked functions,gcc上有各種atomic builtins

0

你應該問的問題是「是 - 原子」?這就是所有重要的事情。你想做些什麼時,標誌達到0

你不關心這樣的場景:

1. Main thread reads flag, and it is 1. 
2. Worker changes flag with -- 
3. Main thread doesn't see that flag is actually 0. 

因爲在1納秒,主線程繞一圈並再次嘗試。

你關心 - 不是原子和兩個線程在同一時間更改它會跳過遞減:

1. Thread A reads flag, flag is 2 
2. Thread B reads flag, flag is 2 
3. Thread A decrements its copy of flag, 2, and writes to flag, flag is 1 
4. Thread B decrements its copy of flag, also 2, and writes to flag, flag is 1. 

你失去了一個遞減。你想使用__sync_fetch_and_sub(&標誌,1),它會自動遞減標誌。

最後,旋轉睡眠並不是最好的方式。您想要等待一個條件,請等待signal。讓工作線程在他們意識到已將標誌遞減爲0時提升條件或信號。