Skip to main content

This recipe will show you how to perform basic atomic operations on an object to prevent the race condition without blocking threads.

using System;
using System.Threading;

//
// Performing basic atomic operations
//
// This recipe will show you how to perform basic atomic operations on an object
// to prevent the race condition without blocking threads.
//
// How it works...
//
// When the program runs, it creates three threads that will execute a code in
// the TestCounter method. This method runs a sequence of increment/decrement
// operations on an object. Initially, the Counter object is not thread-safe and
// we get a race condition here. So in the first case, a counter value is not
// deterministic. We could get a zero value; however, if you run the program
// several times, you will eventually get some incorrect nonzero result.
//
// In Chapter 1, Threading Basics, we resolved this problem by locking our
// object, causing other threads to block while one thread gets the old counter
// value, then computes and assigns a new value to the counter. However, if we
// execute this operation in such a way, it cannot be stopped midway; we would
// achieve the proper result without any locking with the help of the
// Interlocked construct. It provides the atomic methods Increment, Decrement,
// and Add for basic math, and it helps us to write the Counter class without
// the use of locking.
//

namespace Chapter2.Recipe1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("Incorrect counter");

            var c = new Counter();

            var t1 = new Thread(() => TestCounter(c));
            var t2 = new Thread(() => TestCounter(c));
            var t3 = new Thread(() => TestCounter(c));
            t1.Start();
            t2.Start();
            t3.Start();
            t1.Join();
            t2.Join();
            t3.Join();

            Console.WriteLine("Total count: {0}", c.Count);
            Console.WriteLine("--------------------------");

            Console.WriteLine("Correct counter");

            var c1 = new CounterNoLock();

            t1 = new Thread(() => TestCounter(c1));
            t2 = new Thread(() => TestCounter(c1));
            t3 = new Thread(() => TestCounter(c1));
            t1.Start();
            t2.Start();
            t3.Start();
            t1.Join();
            t2.Join();
            t3.Join();

            Console.WriteLine("Total count: {0}", c1.Count);
        }

        static void TestCounter(CounterBase c)
        {
            for (int i = 0; i < 100000; i++)
            {
                c.Increment();
                c.Decrement();
            }
        }

        class Counter : CounterBase
        {
            private int _count;

            public int Count
            {
                get
                {
                    return _count;
                }
            }

            public override void Increment()
            {
                _count++;
            }

            public override void Decrement()
            {
                _count--;
            }
        }

        class CounterNoLock : CounterBase
        {
            private int _count;

            public int Count
            {
                get
                {
                    return _count;
                }
            }

            public override void Increment()
            {
                Interlocked.Increment(ref _count);
            }

            public override void Decrement()
            {
                Interlocked.Decrement(ref _count);
            }
        }

        abstract class CounterBase
        {
            public abstract void Increment();

            public abstract void Decrement();
        }
    }
}