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
Anonymous
December 13, 2006
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.
*/
}
}
Anonymous
December 13, 2006
Very nice ;). Do you use the Ceiliing value in other interlocked operations when you are not updating it? Or with a plain old compare? dAnonymous
December 14, 2006
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?Anonymous
December 14, 2006
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).Anonymous
December 19, 2006
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 advanceAnonymous
December 19, 2006
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. dAnonymous
June 29, 2008
Let's say that you allocated a PIRP and sent it down your device stack.  You free the PIRP in the