Creating your own InterlockedXxx operation


Sometimes your design requires an Interlocked operation that is not currently supported by the OS, runtime libraries, or the compiler (as an intrinsic). You then have a choice to make. Either remove all Interlocked operations for that particular field and use a lock or roll you own Interlocked function. Roll your own Interlocked function? Sounded scary to me the first time it was suggested, but in reality it is a rather easy thing to do. Here is the basic algorithm (which I did not invent, it is a well tested and known algorithm)  and a couple of concrete examples

    LONG
InterlockedYyy(
__inout LONG volatile *Target,
[Additional parameters as needed for operation Yyy]
)
{
LONG prevValue, prevCopy;

prevValue = *Target;

do {
[Optional comparison]

prevCopy = prevValue;

//
// prevValue will be the value that used to be Target if the exchange was made
// or its current value if the exchange was not made.
//
prevValue = InterlockedCompareExchange(Target, Yyy operation on prevCopy, prevValue);

//
// If prevCopy == prevValue, then no one updated Target in between the deref at the top
// and the InterlockecCompareExchange afterward and we are done
//
} while (prevCopy != prevValue);

//
// [value] can be anything you want, but it is typically either
// a) The new value stored in Target. This is the type of return value that
// InterlockedIncrement returns
// or
// b) The new value is the previous value that was in Target. This si the
// type of return value that InterlockedOr or InterlockedExchange return
//
return [value];
}


From this algorithm you can implement nearly anything you want. For instance, let’s say that you want to increment Target if and only if it’s current value is greater then zero. This allows you to never go from zero to one, a common problem when you implement a reference count. Since this Interlocked operation can “fail,” we need a way to convey failure.  In this case I chose to return the Floor value since it is an not a value that could ever be returned in the success path.  Algorithm specific modifications to the generic algorithm are in red

    LONG
MyInterlockedIncrementWithFloor(
__inout LONG volatile *Target,
LONG Floor
)
{
LONG prevValue, prevCopy;

prevValue = *Target;

do {
if (prevValue <= Floor) {
return Floor;
}

prevCopy = prevValue;

//
// prevValue will be the value that used to be Target if the exchange was made
// or its current value if the exchange was not made.
//
prevValue = InterlockedCompareExchange(Target, prevCopy+1, prevValue);

//
// If prevCopy == prevValue, then no one updated Target in between the deref at the top
// and the InterlockecCompareExchange afterward and we are done
//
} while (prevCopy != prevValue);

//
// prevCopy is the old value of Target. Since InterlockedIncrement returns the new
// incremented value of Target, we should do the same here.
//
return prevCopy+1;
}


Let’s say you wanted to XOR in a value in the high word of the Target and clear the low word, you could implement it this way

    LONG
MyInterlockedXorHighClearLowWord(
__inout LONG volatile *Target,
SHORT HighWord
)
{
LONG prevValue, prevCopy;

prevValue = *Target;

do {
// No conditional check like the previous example

            prevCopy = prevValue;

//
// prevValue will be the value that used to be Target if the exchange was made
// or its current value if the exchange was not made.
//
prevValue = InterlockedCompareExchange(
Target,
(prevCopy ^ (HighWord << 16) & (~0xFFFF),
prevValue);

//
// If prevCopy == prevValue, then no one updated Target in between the deref at the top
// and the InterlockecCompareExchange afterward and we are done
//
} while (prevCopy != prevValue);

//
// prevCopy is the old value of Target. Returns the old value of Target
// (just to demonstrate the 2nd pattern for which return type you can use)
//
return prevCopy;
}

Comments (7)

  1. DavidPsurny says:

    Definitely cool ideas.  Here is a similar function I wrote that atomically increases a ceiling value to a new higher value.  It basically works the same as yours…

    /* Atomically raise a high water mark value to a new higher level.  */

    VOID
    AtomicIncreaseCeiling(
       IN OUT  PLONG        Destination,
       IN      LONG         NewCeiling)
    {
       LONG                 OldCeiling;
       LONG                 Temp;

       /* Take a snapshot of the current ceiling value */
       Temp = *Destination;

       while (Temp < NewCeiling)
       {
           /*
              NewCeiling is greater than our snapshot so
              lets try to set a new ceiling value.
           */
           OldCeiling = Temp;

           Temp = InterlockedCompareExchange(Destination, NewCeiling, OldCeiling);

           if (Temp == OldCeiling)
           {
               /*
                 Nobody snuck in and increased the ceiling before we did, we are done
               */
               return;
           }

           /*
             If we get here is means another thread snuck in and increased the
             ceiling before we could.  We will loop again using Temp as the
             snapshot of the updated ceiling value.
           */
       }
    }

     

  2. Very nice ;).  Do you use the Ceiliing value in other interlocked operations when you are not updating it?  Or with a plain old compare?

    d

  3. DavidPsurny says:

    Thanks.  The ceiling value is maintained by this function only, it does not get modified or compared anywhere else.  Periodically, a user mode app will retreive the value via an ioclt and display it on a GUI.  In the ioctl handler I just copy the value into the Irps buffer.  I’m curious as to why you ask about the compare?

  4. I asked b/c you have guaranteed that your value will be written to the Ceiling value wrt other writes to Ceiling, but it does not guarantee that the value you wrote to Ceiling will be used in other compares where the value of Ceiling will be used (e.g. reads).

  5. Emilka says:

    I have share array of shorts readble from user-mode.

    I want to use interlockedIncrement func to increment one of arrays values into DPC .

    is it correct example ?

    //suppose I want to increment arr[37] value

    unsigned short arr[100];

    LPLONG pval;

    pval = (LPLONG)&arr[37];

    InterlockedIncrement(pval );

    MSDN:The variable pointed to by the pval parameter must be aligned on a 32-bit boundary;

    above example works good, but it makes sence

    that value arr[38] could be overwritten.

    What is the correct way to

    InterlockedIncrement short variables?

    thank you in advance

  6. Emilika, you can’t use interlocked operations on a SHORT value, it must be LONG.  You must either declare the array as a LONG or create some other synchronization mechanism to access the array.  If I understand what you are saying correctly, you are sharing the array between your driver and application.  I would strongly suggest that you do not share memory in this way and, instead, keep the array in the driver and provide an IOCTL interface to update/retrieve a copy of the values.

    d

  7. Let’s say that you allocated a PIRP and sent it down your device stack.&#160; You free the PIRP in the