BlueSky
BlueSky

Reputation: 1539

How do you iterate through an array of indices in a thread-safe, performant way?

I have multiple threads that need to write to an Azure queue. To increase throughput, I've created 8 different Azure queues. As a result, I have 8 different connection strings stored in a static string[] array. I'd like to write code that perfectly load balances writing to those queues, by always returning the index of the next queue to write to (and connection string to use), so that queue 0 gets written to the same number of times as queue 1, queue 2, etc. - all in serial succession. I need a way to iterate through indices 0 through 7, in a way that is thread-safe. Is there a more performant and efficient way to write the below code, and how do I make it thread-safe? Thank you very much.

    private static int currentQueueIndex = -1; // initial value set to -1 so that index 0 is returned first.
    private static int maxIndex = 7;
    private static int GetNextQueueIndex()
    {
        int tempIndex = currentQueueIndex + 1;
        if (tempIndex > maxIndex)
        {
            currentQueueIndex = 0;
            return 0;
        }
        else
        {
            return tempIndex;
        }
    }

Upvotes: 2

Views: 101

Answers (2)

BlueSky
BlueSky

Reputation: 1539

Thanks to @Cory Nelson, the following is the final solution:

    /// <summary>
    /// This will return 0 through 7 continuously in a thread-safe manner.
    /// </summary>
    /// <returns>0, 1, 2, 3, 4, 5, 6, 7, and then back to 0.</returns>
    private const int maxIndex = 7; // warning - must be a ([power-of-two number] - 1)
    private static int currentQueueIndex = -1;
    private static int GetNextQueueIndex()
    {
        return Interlocked.Increment(ref currentQueueIndex) & (maxIndex);
    }

Upvotes: 0

Cory Nelson
Cory Nelson

Reputation: 30001

This is a perfect job for the Interlocked methods, which perform atomic operations on values in memory. This is as fast as you can get when it comes to a globally-visible thread-safe operation.

If you can restrict your queue count to power-of-two numbers, you just mask off the low bits:

private static int GetNextQueueIndex()
{
    return Interlocked.Increment(ref currentQueueIndex) & (maxIndex - 1);
}

Or, if you have a non-power-of-two number of queues -- but you asked for perf, so I suggest avoiding a div:

private static int GetNextQueueIndex()
{
    int i = Interlocked.Increment(ref currentQueueIndex) & Int32.MaxValue;
    return i % maxIndex;
}

Note there is no need to reset currentQueueIndex back to 0 -- it's perfectly safe to let the storage overflow and mask off just the bits we need.

Upvotes: 3

Related Questions