atlaste
atlaste

Reputation: 31116

Counting stuff in multiple threads

In my .NET program, I want to count the number of times a piece of code will be hit. To make it a bit more challenging, my code is usually executed in multiple threads and I cannot control the creation / destruction of threads (and don't know when they are created)... they can even be pooled. Say:

class Program
{
    static int counter = 0;

    static void Main(string[] args)
    {
        Stopwatch sw = Stopwatch.StartNew();

        Parallel.For(0, 100000000, (a) =>
            {
                Interlocked.Increment(ref counter);
            });

        Console.WriteLine(sw.Elapsed.ToString());
    }
}

As the performance counter and method are hit quite a few times, I'd like to use a 'normal' variable in contrast to an atomic / interlocked integer. My second attempt was therefore to use threadlocal storage in combination with IDisposable to speed things up. Because I cannot control creation/destruction, I have to keep track of the storage variables:

class Program
{
    static int counter = 0;

    // I don't know when threads are created / joined, which is why I need this:
    static List<WeakReference<ThreadLocalValue>> allStorage = 
        new List<WeakReference<ThreadLocalValue>>();

    // The performance counter
    [ThreadStatic]
    static ThreadLocalValue local;

    class ThreadLocalValue : IDisposable
    {
        public ThreadLocalValue()
        {
            lock (allStorage)
            {
                allStorage.Add(new WeakReference<ThreadLocalValue>(this));
            }
        }

        public int ctr = 0;

        public void Dispose()
        {
            // Atomic add and exchange
            int tmp = Interlocked.Exchange(ref ctr, 0); // atomic set to 0-with-read
            Interlocked.Add(ref Program.counter, tmp); // atomic add
        }

        ~ThreadLocalValue()
        {
            // Make sure it's merged.
            Dispose();
        }
    }

    // Create-or-increment
    static void LocalInc()
    {
        if (local == null) { local = new ThreadLocalValue(); } 
        ++local.ctr;
    }

    static void Main(string[] args)
    {
        Stopwatch sw = Stopwatch.StartNew();

        Parallel.For(0, 100000000, (a) =>
            {
                LocalInc();
            });

        lock (allStorage)
        {
            foreach (var item in allStorage)
            {
                ThreadLocalValue target;
                if (item.TryGetTarget(out target))
                {
                    target.Dispose();
                }
            }
        }

        Console.WriteLine(sw.Elapsed.ToString());

        Console.WriteLine(counter);
        Console.ReadLine();
    }
}

My question is: can we do this faster and/or prettier?

Upvotes: 3

Views: 3320

Answers (1)

mg30rg
mg30rg

Reputation: 1349

What you need is a thread-safe, nonblocking, volatile, static variable to perform the counting for you.


Thanks goodness, the .NET framework provides managed ways to perform what you want.

For starters, you need a volatile, static variable to be used as a counter. Declare it like (where all your threads can access it):

public static volatile int volatileCounter; 

Where static means this is a class and not an instance member, and volatile prevents caching errors from happening.

Next, you will need a code that increments it in a thread-safe and nonblocking way. If you don't expect your counter to exceed the limits of the int variable (which is very likely), you can use the Interlocked class for that like:

Interlocked.Increment(ref yourInstance.volatileCounter);

The interlocked class will guarantee that your increment operation will be atomic so no race condition can cause false results, and it is also non-blocking in the manner of on heavy-weighted sync objects and thread blocking is involved here.

Upvotes: 1

Related Questions