Ghita
Ghita

Reputation: 4505

Decrement atomic counter - but <only> under a condition

I want to realize something on this lines:

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

How can I do this so that both operations are atomic as a block, without using locks ?

Upvotes: 3

Views: 2505

Answers (5)

James Kanze
James Kanze

Reputation: 153899

The simplest solution is just to use a mutex around the entire section (and for all other accesses to this->pendingWorkItems). If for some reason this isn't acceptable, then you'll probably need compare and exchange:

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

(This supposes that pendingWorkItems has type std::atomic_int.)

Upvotes: 2

Pete
Pete

Reputation: 4812

Use an atomic CAS. http://msdn.microsoft.com/en-us/library/windows/desktop/ms683560(v=vs.85).aspx

You can make it lock free, but not wait free.

As Kirill suggests this is similar to a spin lock in your case.

I think this does what you need, but I'd recommend thinking through all the possibilities before going ahead and using it as I have not tested it at all:

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;
}

There remains the question as to why your pending work items should ever go below zero. You should really ensure that the number of increments matches the number of decrements and all will be fine. I'd perhaps add an assert or exception if this constraint is violated.

Upvotes: 0

jxh
jxh

Reputation: 70372

You can use InterlockedCompareExchange in a loop:

    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;
        }
    }

What the InterlockedCompareExchange function is doing is:

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

This is done atomically, and is also called a compare and swap.

Upvotes: 0

Kirill Kobelev
Kirill Kobelev

Reputation: 10557

There is such a thing called "SpinLock". This is a very lightweight synchronisation.

This is the idea:

//
//    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;

};

Actual numbers in the sample are not important. This lock is extremely efficient.

Upvotes: 0

Alexey Frunze
Alexey Frunze

Reputation: 62048

You can just check the result of InterlockedDecrement() and if it happens to be negative (or <= 0 if that's more desirable) undo the decrement by calling InterlockedIncrement(). In otherwise proper code that should be just fine.

Upvotes: 2

Related Questions