Multithreading Interview Questions (Advanced) C#.NET

Multithreading Interview Questions and Answers

In this article, I am going to discuss the most frequently asked Advanced Multithreading Interview Questions and Answers on C#.NET. Please read our last article where we discussed the basic Multi-Threading Interview Questions and Answers on C#.NET.

What is the Significance of Thread.Join and Thread.IsAlive functions in multithreading?

Join blocks the current thread and makes it wait until the thread on which Join method is invoked completes. Join method also has an overload where we can specify the timeout. If we don’t specify the timeout the calling thread waits indefinitely, until the thread on which Join() is invoked completes. This overloaded Join (int millisecondsTimeout) method returns boolean true if the thread has terminated otherwise false. Join is particularly useful when we need to wait and collect result from a thread execution or if we need to do some cleanup after the thread has completed.

The IsAlive returns boolean True if the thread is still executing otherwise false. 

Program code used in the demo: 
namespace MultithreadingDemo
{
    class Program
    {
        public static void Main()
        {
            Console.WriteLine("Main Thread Started" + Thread.CurrentThread.Name);

            Thread T1 = new Thread(Program.Thread1Function);
            T1.Start();

            Thread T2 = new Thread(Program.Thread2Function);
            T2.Start();

            //if (T1.Join(1000))
            //{
            //    Console.WriteLine("Thread1Function completed");
            //}
            //else
            //{
            //    Console.WriteLine("Thread1Function hot not completed in 1 second");
            //}

            T1.Join();
            T2.Join();
            Console.WriteLine("Thread2Function completed");
            for (int i = 1; i <= 10; i++)
            {
                if (T1.IsAlive)
                {
                    Console.WriteLine("Thread1Function is still doing it's work");
                    Thread.Sleep(500);
                }
                else
                {
                    Console.WriteLine("Thread1Function Completed");
                    break;
                }
            }

            Console.WriteLine("Main Thread Completed");
            Console.ReadLine();
        }
        public static void Thread1Function()
        {
            Console.WriteLine("Thread1Function started");
            Thread.Sleep(5000);
            Console.WriteLine("Thread1Function is about to return");
        }
        public static void Thread2Function()
        {
            Console.WriteLine("Thread2Function started");
        }
    }
}
What happens if shared resources are not protected from concurrent access in a multithreaded program?

This is one the most frequently asked Multithreading Interview Questions in C#.NET. The output or behavior of the program can become inconsistent. Let us understand this with an example. 

namespace MultithreadingDemo
{
    class Program
    {
        static int Total = 0;
        public static void Main()
        {
            AddOneMillion();
            AddOneMillion();
            AddOneMillion();
            Console.WriteLine("Total = " + Total);
            Console.ReadLine();
        }
        public static void AddOneMillion()
        {
            for (int i = 1; i <= 1000000; i++)
            {
                Total++;
            }
        }
    }
}

OUTPUT

Total = 3000000

The above program is a single-threaded program. In the Main() method, AddOneMillion() method is called 3 times, and it updates the Total field correctly as expected, and finally prints the correct total i.e. 3000000.

Now, let’s rewrite the program using multiple threads.
namespace MultithreadingDemo
{
    class Program
    {
        static int Total = 0;
        public static void Main()
        {
            Thread thread1 = new Thread(Program.AddOneMillion);
            Thread thread2 = new Thread(Program.AddOneMillion);
            Thread thread3 = new Thread(Program.AddOneMillion);

            thread1.Start();
            thread2.Start();
            thread3.Start();

            thread1.Join();
            thread2.Join();
            thread3.Join();
            Console.WriteLine("Total = " + Total);
            Console.ReadLine();
        }

        public static void AddOneMillion()
        {
            for (int i = 1; i <= 1000000; i++)
            {
                Total++;
            }
        }
    }
}

Every time we run the above program, we get a different output. The inconsistent output is because the Total field which is a shared resource is not protected from concurrent access by multiple threads. The operator ++ is not thread-safe.

How to protect shared resources from concurrent access?

This Multithreading Interview Questions asked in almost all interviews. So let discuss this in details

There are several ways to protect shared resources from concurrent access. Let’s explore 2 of the options.

Using Interlocked.Increment() method: Modify AddOneMillion() method as shown below. The Interlocked.Increment() Method, increments a specified variable and stores the result, as an atomic operation

public static void AddOneMillion()
{
    for (int i = 1; i <= 1000000; i++)
    {
        Interlocked.Increment(ref Total);
    }
}
The other option is to use a lock.
static object _lock = new object();
public static void AddOneMillion()
{
    for (int i = 1; i <= 1000000; i++)
    {
        lock (_lock)
        {
            Total++;
        }
    }
}
Which option is better?

From a performance perspective using Interlocked class is better over using locking. Locking locks out all the other threads except a single thread to read and increment the Total variable. This will ensure that the Total variable is updated safely. The downside is that since all the other threads are locked out, there is a performance hit.

The Interlocked class can be used with addition/subtraction (increment, decrement, add, etc.) on an int or long field. The Interlocked class has methods for incrementing, decrementing, adding, and reading variables atomically.

The following code prints the time taken in ticks. 1 millisecond consists of 10000 ticks.

public static void Main()
{
    Stopwatch stopwatch = Stopwatch.StartNew();
    Thread thread1 = new Thread(Program.AddOneMillion);
    Thread thread2 = new Thread(Program.AddOneMillion);
    Thread thread3 = new Thread(Program.AddOneMillion);
    thread1.Start();
    thread2.Start();
    thread3.Start();
    thread1.Join();
    thread2.Join();
    thread3.Join();
    Console.WriteLine("Total = " + Total);
    stopwatch.Stop();
    Console.WriteLine("Time Taken in Ticks = " + stopwatch.ElapsedTicks);
}

Please Note: You can use the TimeSpan object to find ticks per second, ticks per millisecond etc. Stopwatch class is in System.Diagnostics namespace. 

What are Interlocked functions?

Interlocked functions in .NET are useful in multithreading programs to safely change the value of shared variables. By default, C# variables are not thread-safe. When we apply addition, subtraction or checking the value of variables multiple threads can corrupt the variables. For preventing these dirty reads, we can use Interlocked functions.

Interlocked functions can only work on int, long, double and float data types

What is Lock?

The Lock is another synchronization mechanism in C# and one of the famous multi-threading interview questions in .NET. It restricts the critical region so that only one thread can enter a critical region at a time.

Lock needs an object to continue its operation. It applies a lock on a target object and only one thread can lock that target object at a time

What is the Difference between Monitor and lock in C#?

I think you need to be prepared this Multithreading Interview Questions and Answer if you are preparing to attend the interview questions on Multithreading. So let us discuss this question in details

  1. The Lock is just a shortcut for Monitor statement. Compiler internally converts lock statement to Monitor.Enter and Exit statements.
  2. Monitor class provides some useful method which is not in lock statement. These methods are very useful in advanced scenarios.
  3. The monitor provides TryEnter method. This method is useful when we need to provide a timeout value.
  4. TryEnter is also useful when we have to check whether the lock is taken or not. We can pass a boolean parameter which returns true if the lock is taken else returns false.
  5. The Pulse method notifies a waiting thread of a change in the locked object’s state.
  6. Wait method releases the current acquired lock and blocks the current thread until it reacquires the lock.

Both Monitor class and lock provides a mechanism to protect the shared resources in a multithreaded application. The lock is the shortcut for Monitor.Enter with the try and finally. 

This means that the following code 
static object _lock = new object();
public static void AddOneMillion()
{
    for (int i = 1; i <= 1000000; i++)
    {
        lock (_lock)
        {
            Total++;
        }
    }
}

can be rewritten as shown below:

static object _lock = new object();
public static void AddOneMillion()
{
    for (int i = 1; i <= 1000000; i++)
    {
        // Acquires the exclusive lock
        Monitor.Enter(_lock);
        try
        {
            Total++;
        }
        finally
        {
            // Releases the exclusive lock
            Monitor.Exit(_lock);
        }
    }
}
From C# 4, it is implemented slightly differently as shown below
static object _lock = new object();
public static void AddOneMillion()
{
    for (int i = 1; i <= 1000000; i++)
    {
        bool lockTaken = false;
        // Acquires the exclusive lock
        Monitor.Enter(_lock, ref lockTaken);
        try
        {
            Total++;
        }
        finally
        {
            // Releases the exclusive lock
            if (lockTaken)
                Monitor.Exit(_lock);
        }
    }
}

So, in short, the lock is a shortcut and it’s the option for the basic usage. If we need more control to implement advanced multithreading solutions using TryEnter(), Wait(), Pulse(), & PulseAll() methods, then the Monitor class is our option. 

SUMMARY

In this article, I try to explain most frequently asked Advanced Multithreading Interview Questions and Answers on C#.NET. I hope this article will help you with your need. I would like to have your feedback. Please post your feedback, question, or comments about this article.

Leave a Reply

Your email address will not be published. Required fields are marked *