2012-07-11 29 views
3

我想實現這個東西線下:遞減原子計數器 - 但<only>條件

 inline void DecrementPendingWorkItems() 
     { 
      if(this->pendingWorkItems != 0) //make sure we don't underflow and get a very high number 
      { 
       ::InterlockedDecrement(&this->pendingWorkItems); 
      } 
     } 

我怎麼能做到這一點,使這兩個操作都是原子作爲一個塊,而無需使用鎖?

+0

無關你的實際問題,但如果不要'pendingWorkItems'爲零或負數,你應該使用'>'比較運算符來代替。它可以保護'pendingWorkItems'變爲負數的錯誤。 – 2012-07-11 08:46:58

+0

@JoachimPileborg變量無符號使用的API所需的, – Ghita 2012-07-14 18:34:43

+0

@Ghita'InterlockedDecrement'將指針指向'LONG',它實際上是一個有符號值。 – dgnuff 2016-04-08 18:34:07

回答

0

有這樣的事情叫做「SpinLock」。這是一個非常輕量級的同步。

這的理念是:樣品中

// 
// This lock should be used only when operation with protected resource 
// is very short like several comparisons or assignments. 
// 
class SpinLock 
{ 
public: 

     __forceinline SpinLock() { body = 0; } 
     __forceinline void Lock() 
      { 
      int spin = 15; 
      for(;;) { 
       if(!InterlockedExchange(&body, 1)) break; 
       if(--spin == 0) { Sleep(10); spin = 29; } 
      } 
      } 

     __forceinline void Unlock() { InterlockedExchange(&body, 0); } 

protected: 

    long body; 

}; 

實際數量並不重要。這個鎖非常有效。

+0

身體應該是'volatile'不應該嗎? – smerlin 2012-07-11 09:39:33

+0

不,不是。這工作在生產多年。討論揮發性可能發生在這裏,但不幸的是它不符合評論的格式。 – 2012-07-11 09:42:47

+0

你應該旋轉易失性讀取,並且只有在讀取表示嘗試獲取鎖的時間時才使用CAS。此外,你應該總是使用'_mm_pause'來發出自旋循環處理器提示,否則你會不必要地燒掉你的CPU(並且使用非線性補償)。 – Necrolis 2012-07-11 10:39:29

0

你可以在一個循環中使用InterlockedCompareExchange

inline void DecrementPendingWorkItems() { 
     LONG old_items = this->pendingWorkingItems; 
     LONG items; 
     while ((items = old_items) > 0) { 
      old_items = ::InterlockedCompareExchange(&this->pendingWorkItems, 
                items-1, items); 
      if (old_items == items) break; 
     } 
    } 

什麼InterlockedCompareExchange功能正在做的是:

if pendingWorkItems matches items, then 
    set the value to items-1 and return items 
    else return pendingWorkItems 

這是自動完成的,並且也被稱爲比較和交換

2

你可以只檢查InterlockedDecrement()的結果,如果它恰好是負的(或< = 0,如果這是更可取)通過調用InterlockedIncrement()撤消遞減。在其他適當的代碼應該是很好的。

0

使用原子CAS。 http://msdn.microsoft.com/en-us/library/windows/desktop/ms683560(v=vs.85).aspx

您可以使它免費鎖定,但不能等待空閒。

正如Kirill所說,這與您的情況下的自旋鎖相似。

我認爲這確實你需要什麼,但我建議纔去進取,使用它在所有的可能性思想爲我沒有測試它在所有:

inline bool 
InterlockedSetIfEqual(volatile LONG* dest, LONG exchange, LONG comperand) 
{ 
    return comperand == ::InterlockedCompareExchange(dest, exchange, comperand); 
} 

inline bool InterlockedDecrementNotZero(volatile LONG* ptr) 
{ 
    LONG comperand; 
    LONG exchange; 
    do { 
     comperand = *ptr; 
     exchange = comperand-1; 
     if (comperand <= 0) { 
      return false; 
     } 
    } while (!InterlockedSetIfEqual(ptr,exchange,comperand)); 
    return true; 
} 

仍然存在至於問題爲什麼你的未決工作項目應該低於零。您應該確保增量的數量與減量的數量相匹配,並且一切正常。如果違反了這個約束,我可能會添加一個斷言或異常。

+0

小提示:對於可能的競態條件使用標準C++ assert是無用的,因爲許多競爭條件只會在發佈版本中發生,並且在發佈版本中不會啓用「assert」。應該爲所有配置中啓用的這種情況編寫自己的斷言版本。 – smerlin 2012-07-11 09:18:24

+0

同意。例外情況會更好。 – Pete 2012-07-11 09:27:04

+0

@smerlin如果下溢發生的原因(可能是一個bug)我不希望這導致另一個更危險的錯誤在我的情況。這只是一個失敗保險箱。 – Ghita 2012-07-14 18:38:41

2

最簡單的解決方案是圍繞整個部分 (以及對this->pendingWorkItems的所有其他訪問)使用互斥鎖。如果由於某種原因 這是不能接受的,那麼你可能需要比較和交換 :

void decrementPendingWorkItems() 
{ 
    int count = std::atomic_load(&pendingWorkItems); 
    while (count != 0 
      && ! std::atomic_compare_exchange_weak( 
        &pendingWorkItems, &count, count - 1)) { 
    } 
} 

(此假設是pendingWorkItems具有類型std::atomic_int。)