Chapter 3 : Thread Synchronization by muskad202

Lets start with a simple application.

Have a look at the code below:

    class BankAccount
    {
        public int Money;
    }

    class Program
    {
        static void Main (string[] args)
        {

            BankAccount account = new BankAccount();
            account.Money = 100;
            Thread oneDollarThread = new Thread(new ParameterizedThreadStart(AddOneDollar));
            Thread twoDollarThread = new Thread(new ParameterizedThreadStart(AddTwoDollars));
            oneDollarThread.Start(account);
            twoDollarThread.Start(account);
            Thread.Sleep(4000);
            Console.WriteLine("Amount of money in account = " + account.Money);
        }

        static void AddOneDollar(object param)
        {

            BankAccount account = (BankAccount)param;
            for (int i = 1; i <= 100; i++)
            {
                int oldamount = account.Money;
                oldamount = oldamount + 1;
                Thread.Sleep(1);
                account.Money = oldamount;
            }
        }

        static void AddTwoDollars(object param)
        {

            BankAccount account = (BankAccount)param;
            for (int i = 1; i <= 100; i++)
            {
                int oldamount = account.Money;
                oldamount = oldamount + 2;
                Thread.Sleep(1);
                account.Money = oldamount;
            }
        }
    }

In the main function, we create an object of the BankAccount class, and set the initial value of the money field to 100. We then create two threads. The first thread executes the function AddOneDollar, while the second thread executes the function AddTwoDollars. Each of these 2 functions takes as a parameter the BankAccount object we created, and within a loop that iterates 100 times, increases the amount each time by either 1 or 2 dollars.

In the main function, we execute Thread.Sleep(4000) in order to make the main thread sleep (pause) for 4 seconds – this is to give the other two threads ample time to execute. In each of the other two threads, we execute Thread.Sleep(1), this is in order to simulate some sort of processing activity going on within the Bank’s system.

At the end of the 4 seconds, when the main thread wakes up, how much money do you expect to be in the BankAccount? 400?

Have a look at the output I got (your output might be different):

 

300? What went wrong? Each loop executed 100 times, so, theoretically, the total amount should have been 400 (initially 100, plus 1 * 100, plus 2 * 100).

The reason for the incorrect result is not that hard to determine.

Let us assume that your computer has only one processor (this is the most common scenario). As a result, your computer can only perform one operation at a time. Even though you might be having multiple applications running, with each application having multiple threads running, your computer still performs ONLY ONE operation at a time. Then how do all your applications work simultaneously? Simple – your computer switches between all the running applications very fast. It might allocate a few milliseconds of the processor time to application A to execute. After these few milliseconds, the Operating System pauses application A, and then informs application B to run. After another few milliseconds, the Operating System pauses application B, and then tells application C to run. This whole process happens really fast, and to you – the user – you get the feeling that your computer is actually running all your applications at the exact same time.

Again, within an application, only one thread can run at a time. In our code above, let us assume that at present, the thread corresponding to the function AddOneDollar is runnin, and that the thread corresponding to AddTwoDollars has yet not run. Let us assume that we are in our first iteration (i.e., i=1), and the following two lines have completed executing:

int oldamount = account.Money;

oldamount = oldamount + 1;

Thread.Sleep(1);

At this point, the amount in our BankAccount is yet 100, since we haven’t had a chance to update it. The local variable oldamount however has a value of 101.

After the 3rd line above was executed, let us assume that the Operating System paused this thread, and resumed our other thread, corresponding to AddTwoDollars (since the Operating System periodically pauses the currently running thread in order to give other threads a chance to execute). As a result, assume that the following lines got executed:

int oldamount = account.Money;
oldamount = oldamount + 2;
Thread.Sleep(1);
account.Money = oldamount;

Guess what happens? The first line reads the value of account.Money as 100 (since the value was yet not updated by the first thread) into the local variable oldamount. It increases the value to 102. It then updates the Money field in our global account variable to 102. Assume that after this happens, the Operating System pauses this thread, and goes and wakes up our first thread. The first thread, unaware of what has been going on, goes and updates the Money field to 101! And so, we have a problem.

Synchronization

We need some sort of synchronization mechanism, whereby, when we have any variable (or any resource for that matter – a file on the hard disk for example) which is simultaneously being read from and written to by multiple threads, we can be sure that the data in that variable or resource will remain consistent.

Have a look at the following code (better still, create a new console application project, and use the following code):

    class BankAccount
    {
        public int Money;
    }

    class Program
    {
        static Mutex myMutex;
        static void Main (string[] args)
        {

            BankAccount account = new BankAccount();
            account.Money = 100;
            myMutex = new Mutex();
            Thread oneDollarThread = new Thread(new ParameterizedThreadStart(AddOneDollar));
            Thread twoDollarThread = new Thread(new ParameterizedThreadStart(AddTwoDollars));
            oneDollarThread.Start(account);
            twoDollarThread.Start(account);
            Thread.Sleep(4000);
            Console.WriteLine("Amount of money in account = " + account.Money);
        }

        static void AddOneDollar(object param)
        {

            BankAccount account = (BankAccount)param;
            for (int i = 1; i <= 100; i++)
            {
                myMutex.WaitOne();
                int oldamount = account.Money;
                oldamount = oldamount + 1;
                Thread.Sleep(1);
                account.Money = oldamount;
                myMutex.ReleaseMutex();
            }

        }

        static void AddTwoDollars(object param)
        {

            BankAccount account = (BankAccount)param;
            for (int i = 1; i <= 100; i++)
            {
                myMutex.WaitOne();
                int oldamount = account.Money;
                oldamount = oldamount + 2;
                Thread.Sleep(1);
                account.Money = oldamount;
                myMutex.ReleaseMutex();
            }
        }
    }

Run it. Let’s look at the output I got:

Ok, things seem back to normal. Let’s now look at the code.

The first thing I’ve done differently is to create an object of the class Mutex. The word mutex comes from “mutual exclusion” – it is used to ensure that a piece of code which is being executed, is being executed at any instant in time by at most one thread. So how do we use this object to ensure this? Have a look at the rest of the code.

A Mutex variable can be “owned” by at most one thread at any instant of time. When we initially created the mutex, it was not owned by anyone (rather, it was not owned by any thread). Whenever the AddOneDollar or the AddTwoDollars function want to update the Money field in the global account variable, we first execute the line

myMutex.WaitOne();

What this means is, we want the current thread to try and take ownership of the Mutex variable. If at that instant, any other thread has the ownership of the Mutex variable, then the current thread will be “blocked” (i.e., the WaitOne() function will not return) until the thread that owns the Mutex “releases” it. Once the current thread obtains the ownership of the Mutex, the WaitOne() function returns. Execution then proceeds to the next line, where we read the Money field, and increment it. Now, suppose that before the thread was able to update the Money field, the Operating System paused it (in order to give other threads a chance to execute), and gave control to our other thread. However, our other thread will reach the line

myMutex.WaitOne();

and get “blocked”, since the mutex is at present owned by our first thread, which has yet not given up ownership of it. Our first thread, when it ultimately gets back control, will then update the Money field, and then execute

myMutex.ReleaseMutex()

By now you should be able to figure out what the above line does. Yes, it releases ownership of the mutex, allowing our other thread to obtain ownership of it. In this way, a mutex can be used to synchronize access to shared resources.

The “lock” syntax

What if you forget to Release a mutex? Simple answer – your code isn’t going to run properly. You need to make sure that you always release your mutexes, even when exceptions are thrown from code that gets executed between the WaitOne() and ReleaseMutex() calls. Bottom line – you need to make sure that all paths that your code follows always release any mutexes which the code might have taken ownership of.

Since this is a slightly cumbersome problem (but a much more difficult one to debug), there is another synchronization mechanism which you can use. Have a look at the code below:

    class BankAccount
    {
        public int Money;
    }

    class Program
    {

        static object lockObject;
        static void Main (string[] args)
        {

            BankAccount account = new BankAccount();
            account.Money = 100;
            lockObject = new object();
            Thread oneDollarThread = new Thread(new ParameterizedThreadStart(AddOneDollar));
            Thread twoDollarThread = new Thread(new ParameterizedThreadStart(AddTwoDollars));

            oneDollarThread.Start(account);
            twoDollarThread.Start(account);
            Thread.Sleep(4000);

            Console.WriteLine("Amount of money in account = " + account.Money);
        }

        static void AddOneDollar(object param)
        {

            BankAccount account = (BankAccount)param;
            for (int i = 1; i <= 100; i++)
            {
                lock (lockObject)
                {

                    int oldamount = account.Money;
                    oldamount = oldamount + 1;
                    Thread.Sleep(1);

                    account.Money = oldamount;
                }

            }

        }

        static void AddTwoDollars(object param)
        {

            BankAccount account = (BankAccount)param;
            for (int i = 1; i <= 100; i++)
            {

                lock (lockObject)
                {
                    int oldamount = account.Money;
                    oldamount = oldamount + 2;
                    Thread.Sleep(1);
                    account.Money = oldamount;
                }

            }

        }

    }

I’ve created an object called lockObject, which I then use from the AddOneDollar and AddTwoDollar functions. Within these functions, before accessing the global account variable, I execute the line

lock (lockObject)

and then execute the rest of the code within the scope. You can think of this lock statement as something similar to Mutexes – only one thread can lock an object at one time. If a second thread tries to lock an object which has been locked by another thread, then the first thread will be “blocked” until the second thread releases the lock. And when exactly is the lock “released”? You’ll see in the code above that we use curly braces, as if we’re defining a scope. The object remains “locked” as long as the code within the scope is being executed. Once you leave the scope, the lock is automatically “released”. And here comes the advantage of locks over mutexes – the moment you leave the scope, the lock is automatically released – you don’t need to do anything on your part. Whether you leave the scope via a return statement, or whether an exception is thrown, you don’t need to bother – .NET takes care of the lock releasing mechanism for you.