Race Conditions Sample in C#


In a multithreaded program shared resources can be accessed by threads concurrently, this could lead to Race Conditions. For example, ThreadA accesses a shared variable and ThreadB also accesses the same shared variable, and both of them are performing operations on the variable concurrently. They are racing to see which thread can perform the last write on the shared variable, the value of the last write is preserved in the variable. This will lead to unpredictable value in the shared variable as the programmer does not have control on the order in which the shared variable is assessed by the working threads.

using System;
using System.Threading;

namespace CSharp_Samples
{
    public class RaceConditionsSample
    {
        private static int counter = 0;
        public static void Main()
        {            
            // Initialize thread with address of DoWork1
            Thread thread1 = new Thread(DoWork1);

            // Initilaize thread with address of DoWork1
            Thread thread2 = new Thread(DoWork1);

            // Start the Threads.
            thread1.Start();
            thread2.Start();

            thread1.Join();
            thread2.Join();

            Console.WriteLine("Done Processing...");
            Console.WriteLine(counter);
        }

        private static void DoWork1()
        {
            for (int index = 0; index < 2000000; index++)
            {
                counter++;
            }
        }        
    }
}

In the above code, we have two threads having control over a shared resource. In DoWork1 method we have for loop that increments the counter. Both thread1 and thread2 are pointing to DoWork1.

The main method itself is in another thread, so by using the Join method in the main, we are forcing the main method to wait for the secondary threads to finish.

This code is not thread safe as a shared resource can be altered by two different threads at the same time. The output the program is unpredictable.

Since the for loop is run twice (once by each thread) the value of the counter should be 4000000 at the end of the program, however as shared resource is accessed by the threads concurrently the final value in the counter is unpredictable.

For example – Let’s say counter has value 10000. First thread1 reads the value, before increment is performed by thread1, thread2 reads the same value 10000 and both of them write 10001 to the counter. One increment is lost here, this could happen multiple times.

Let’s fix the above race condition problem by using lock keyword.

In the below example, the counter increment operation is wrapped in a lock statement so that only one thread can access counter value at any given time.

using System;
using System.Threading;

namespace CSharp_Samples
{
    public class RaceConditionsSampleFixWithLock
    {
        private static int counter = 0;

        private static object staticObjLock = new object();

        public static void Main()
        {
            // Initialize thread with address of DoWork1
            Thread thread1 = new Thread(DoWork1);

            // Initilaize thread with address of DoWork1
            Thread thread2 = new Thread(DoWork1);

            // Start the Threads.
            thread1.Start();
            thread2.Start();

            thread1.Join();
            thread2.Join();

            Console.WriteLine("Done Processing...");
            Console.WriteLine(counter);
        }

        private static void DoWork1()
        {            
            for (int index = 0; index < 2000000; index++)
            {
                // Actually we can apply lock on entire for loop.
                // This is only for demo purposes.
                // It is not a good idea to lock so many times.
                lock (staticObjLock)
                {
                    counter++;
                }
            }            
        }
    }
}