2012-07-11 36 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。)