Race Condition & Synchronization in C#

Jul 18th, 2010 | By | Category: Programming & Languages

We have heard about the term race condition in relation to threads.This is basically a scenario where two or more threads are competing(racing) to read/write from a shared memory location leading to the possibilities of leaving the program in an inconsistent state.

Consider the program shown below:

public class A
{
     public int x = 0;
     public int y = 0; 

     public void M1()
     {

             if (y == 0)
             {
                 x = y + 1;
             }
             else x = 5;
     }
     public void M2()
     {
             if (x == 0)
             {
                 y = x + 1;
             }
             else y = 5;
     }
}

This a very simple program where the methods M1 and M2 tries to read/write from/to two shared variables x and y.If M1 and M2 are executed using two threads started one after the other we should get an output x=1 and y=5 or x=5 and y=1 depending upon who is scheduled to execute first.

A a = new A(); 

ThreadStart ts1 = new ThreadStart(a.M1);
ThreadStart ts2 = new ThreadStart(a.M2); 

Thread t1 = new Thread(ts1); 

Thread t2 = new Thread(ts2); 

t1.Start(); 

t2.Start(); 

t1.Join();
t2.Join();
Console.WriteLine("x:{0},y:{1}", a.x, a.y);

But when we run it several times we sometimes might see an output where x=1 and y=2 and vice versa.So here what is possibly happening is

  • Thread t1 checks if y==0 and gets true
  • Thread t2 checks x==0 and gets true
  • Thread t2 sets x= y+1 = 0 + 1 = 1
  • Thread t1 sets y=x+1 = 1+1 =2

So to ensure a consistent program output we need to ensure that sections of the code where we read/write from x & y will be executed by one thread at a time.These sections of the code are referred to as critical sections and the mechanisms to protect the critical sections are known as synchronization techniques.

.NET has a provision of doing so using the methods of the System.Threading.Monitor class.The two most commonly used methods are:

  • public static void Enter(Object obj) – This method is called before entering the critical sections and it grants an exclusive lock on the object passed as parameter.
  • public static void Exit(Object obj) – This method is called before leaving the critical sections and it releases the  exclusive lock on the object passed as parameter.

So our modified code  now looks like:

public class A
{
     public int x = 0;
     public int y = 0;
     private object lockObject = new object();
     public void M1()
     {
         Monitor.Enter(lockObject);
         if (y == 0)
         {
             x = y + 1;
         }
         else
         {
             x = 5;
         }
         Monitor.Exit(lockObject); 

     }
     public void M2()
     {
             Monitor.Enter(lockObject);
             if (x == 0)
             {
                 y = x + 1;
             }
             else
             {
                 y = 5;
             }
             Monitor.Exit(lockObject); 

     }

}

When any one of the threads call Monitor.Enter(lockObject) it gets an exclusive lock on the instance of the object.So the second thread has to wait till that lock is released by Monitor.Exit(lockObject).

But from programming perspective this is a bit cumbersome and error prone.What happens if Monitor.Enter is called but not Exit particularly in case of exception situations.So it is a good programming practice to implement Monitor as shown below:

public void M2()
{

    try
    {
        Monitor.Enter(lockObject);
        if (x == 0)
        {
            y = x + 1;
        }
        else
        {
            y = 5;
        }
    }
    finally
    {
        Monitor.Exit(lockObject);
    }
}

C# provides a keyword called lock which helps to implement critical section protection in a much simpler and error free way.

public void M2()
 {
     lock (lockObject)
     {
         if (x == 0)
         {
             y = x + 1;
         }
         else
         {
             y = 5;
         }
     }
 }

The C# compiler will generate the IL code with appropriate calls to Monitor.Enter/Monitor.Exit and try{}finally{} blocks as shown below:

 

.method public hidebysig instance void  M2() cil managed
{
   …………….

  .try
  {
    IL_0003:  ldarg.0
    IL_0004:  dup
    IL_0005:  stloc.1
    IL_0006:  ldloca.s   ‘<>s__LockTaken0′
 
  IL_0008:  call       void [mscorlib]System.Threading.Monitor::Enter(object,
                                                                        bool&)
    …………….
    …………….

  }  // end .try
  finally
  {
    IL_003d:  ldloc.0
    IL_003e:  ldc.i4.0
    IL_003f:  ceq
    IL_0041:  stloc.2
    IL_0042:  ldloc.2
    IL_0043:  brtrue.s   IL_004c
    IL_0045:  ldloc.1
    IL_0046:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
    IL_004b:  nop
    IL_004c:  endfinally
  }  // end handler
  IL_004d:  nop
  IL_004e:  ret
} // end of method A::M2

In the above examples the entire code in M1 and M2 are protected as critical sections.We can say these methods itself are synchronized such that one thread can execute them at a time.We can also implement this using the MethodImpl attribute as shown below:

[System.Runtime.CompilerServices.MethodImpl
(System.Runtime.CompilerServices.MethodImplOptions.Synchronized)]
public void M2()
{

        if (x == 0)
        {
            y = x + 1;
        }
        else
        {
            y = 5;
        }
}

In the IL compiler will add the synchronized keyword as shown below:

.method public hidebysig instance void  M2() cil managed synchronized
{

………….

}

This implementation flag called synchronized tells the JIT compiler to insert code to acquire lock on entry of the method and release the lock on exit of the method.For instance methods this lock is placed on object instance i.e. this parameter and for static methods this lock is placed on System.Type.

So in this post we have discussed about the various ways to implement locking of critical sections.In the next post we will some other mechanisms  of locking which is conceptually little bit different from internal implementation standpoint.


Kick It on DotNetKicks.com
Tags: ,
  • None

    you should not lock(this) but rather a new object you instantiated in your constructor.

  • Sanjeeb Sarkar

    Why do we need to use lock (this) even when we are putting MethodImpl attribute on the method?

  • sankarsan

    Thanks to both of you for your comments.
    I have changed the code.

    Regarding lock(this) someone can refer to http://blogs.msdn.com/b/springboard/archive/200

  • http://codingndesign.com/blog/?p=158 C# Monitor – Some Important Points – Coding N Design

    [...] my last post I started the discussion on race conditions and thread synchronization technique using Monitor/lock [...]