Brannon
Brannon

Reputation: 5414

multi-threaded increment and skip 0 without a lock?

I have a ushort counter (that occasionally rolls over). The messaging protocol that uses this value disallows a 0. I need some thread-safe way to increment this counter (stored in a class field) every time I read it, which isn't hard if I store it as an int and use the Interlocked.Increment. However, I'm not sure how to incorporate skipping 0 into that. It's okay if I occasionally skip a few numbers; my output sequence doesn't have to be perfect. I cannot ever reuse the same number in any block of 4000. I would like to avoid using a lock.

Upvotes: 1

Views: 971

Answers (1)

xanatos
xanatos

Reputation: 111860

This one:

Given:

static int value = ushort.MaxValue;

And in the code:

int temp, temp2;

do
{
    temp = value;
    temp2 = temp == ushort.MaxValue ? 1 : temp + 1;
}
while (Interlocked.CompareExchange(ref value, temp2, temp) != temp);

You'll have to use an int and then cast it (for example in a get property), because the Interlocked aren't for all basic types.

We could probably make it a little faster in highly threaded contexts like this:

int temp = value;

while (true)
{
    int temp2 = temp == ushort.MaxValue ? 1 : temp + 1;

    int temp3 = Interlocked.CompareExchange(ref value, temp2, temp);

    if (temp3 == temp)
    {
        break;
    }

    temp = temp3;
}

In this way we have to do one less read on failure.

As I've written in the comment, the central idea of this code is to increment in a temporary variable (temp2) the counter, and then trying to exchange the old value that we know with the new value (Interlocked.CompareExchange). If no one touched the old value in-between (Interlocked.CompareExchange() == temp) then we have finished. If someone else incremented the value then we do another try. The ushort is simulated by the use of an int with a fixed maximum value (temp == ushort.MaxValue ? 1 : temp + 1).

The second version, on failure of the Interlocked.CompareExchange() reuses the value read by the function as the new basis on which to add 1.

The Interlocked.CompareExchange used in that way can be used as a basis for building other Interlocked operations (you want an Interlocked.Multiply? You do a "standard" multiply and then try to Interlocked.CompareExchange the old value)

Upvotes: 5

Related Questions