Threading Simplified: Part 8 (Synchronization Basics and Thread Blocking)

I am again here to continue the discussion around Threading. Today we will discuss about Thread Synchronization Fundamentals and related concepts.

In case you didn’t have a look at the previous posts, you can go through them below.
Let’s start by posing questions to understand the concepts.

Why Synchronization

When working in multithreaded environments, it’s hard to predict which thread is active and executing any piece of code. This becomes even harder when all the threads are executing shared or common code. So to answer, synchronization is required to predict the outcome in multithreaded environment.

What are the ways to handle synchronization?

There are several ways and methods to handle synchronization as in the following:
  • Blocking
         o Sleep
         o Join
         o Task.Wait
  • Locking
         o Exclusive (Lock or Monitor, Mutex (Local/ System), Spin Lock)
         o Non-exclusive (Semaphore (Local/ System), Semaphore Slim, Reader/Writer Lock)
  • Signaling
         o Event wait handles (AutoResetEvent/ ManualResetEvent)
         o Wait/ Pulse
         o Countdown event
         o Barrier class
  • Non-blocking
         o Memory Barrier
         o Volatile Read/ Write classes
         o Volatile keyword
         o Interlocked class
As you can see thread synchronization has lot of contents and hence we will take them one by one. In this article, we will focus on thread blocking, as it’s easier to understand.
 
Thread Blocking
 
The philosophy behind thread blocking is to let another thread wait until current thread is either finished or has consumed a certain amount of time.
The good part of thread blocking is that the waiting thread doesn’t consume any CPU slice until its reactivated back or unblocked.

Thread reactivation or unblocking can also happen using Thread.Interrupt or Thread.Abort methods.

Just to add, context switching happens when thread blocks or unblocks that adds an overhead of few microseconds.

Let’s talk about one of the important enum called ThreadState that helps in determining if the current thread is blocked or not.

ThreadState(System.Threading) enum has several values but notably are the following.
  • Running
  • Background
  • Stopped
  • Suspended
  • Aborted
  • WaitSleepJoin
Please note that there is one more ThreadState enum in System.Diagnostics namespace so don’t get confused with that. As per MSDN documentation, these should only be used in debugging scenarios as at times these enums don’t yield correct thread state.
 
Now let’s see something in action.

Let’s go through the code to understand how constructs of thread blocking really works.
  1. static Thread thread1 = null;  
  2. static Thread thread2 = null;  
  3.   
  4. static void DoWork1()  
  5. {  
  6.     Console.WriteLine("Inside DoWork1, Thread 1 state: {0}", thread1.ThreadState);  
  7.     Console.WriteLine("Inside DoWork1, Thread 2 state: {0}", thread2.ThreadState);  
  8.   
  9.     for (int i = 0; i < 3; i++)   
  10.     {  
  11.         Console.WriteLine("DoWork1: " + i);  
  12.     }  
  13. }  
  14.   
  15. static void DoWork2()  
  16. {  
  17.     Console.WriteLine("Inside DoWork2, Thread 1 state: {0}", thread1.ThreadState);  
  18.     Console.WriteLine("Inside DoWork2, Thread 2 state: {0}", thread2.ThreadState);  
  19.   
  20.     for (int i = 0; i < 3; i++)  
  21.     {  
  22.         Console.WriteLine("DoWork2: " + i);  
  23.     }  
  24. }  
  25.   
  26. static int DoWork3()  
  27. {  
  28.     ////In real word, some expensive operation will be there.  
  29.     ////Putting sleep to simulate load.  
  30.     Thread.Sleep(1000);  
  31.     return 5;  
  32. }  
  33.   
  34. static void TestJoin()  
  35. {  
  36.     Console.WriteLine("\nTesting Join");  
  37.     thread1 = new Thread(DoWork1);  
  38.     thread2 = new Thread(DoWork2);  
  39.     thread1.Start();  
  40.     thread1.Join();  
  41.     thread2.Start();  
  42. }  
  43.   
  44. static void TestSleep()  
  45. {  
  46.     Console.WriteLine("\nTesting Sleep");  
  47.     Console.WriteLine("Thread sleep started time: {0}", DateTime.Now);  
  48.     Thread.Sleep(2000);  
  49.     Console.WriteLine("Thread sleep ended time: {0}", DateTime.Now);  
  50. }  
  51.   
  52. static void TestTaskWait()   
  53. {  
  54.     Console.WriteLine("\nTesting Task.Wait");  
  55.     Console.WriteLine("Task started time: {0}", DateTime.Now);  
  56.     var task = Task.Factory.StartNew(DoWork3);  
  57.     task.Wait();  
  58.     Console.WriteLine("Task ended time: {0}, Result: {1}", DateTime.Now, task.Result);  
  59. }  
  60.   
  61. static void Main(string[] args)  
  62. {  
  63.     Console.Title = "Thread Synchronization Demo";  
  64.     TestSleep();  
  65.     TestJoin();  
  66.     TestTaskWait();  
  67. }  
In the code, you can see that we have three methods TestSleep, TestJoin and TestTaskWait to understand thread blocking. The following is the output of the code.

Output

Output

You can see in the output that,
  • We have put 2000 ms of sleep time and hence there is 2 seconds of difference in sleep starting time and ending time.
  • Since we have put Join in thread1, thread2 (DoWork2) is executing only once thread1 is finished.
  • The thread state of thread1 is changed from “Running” to “Stopped” when thread2 started “Running”.
  • Task.Wait holds the thread execution until it’s finished (unlike async/await which we will discuss later)
Hope you have liked the article. I look forward for your comments/suggestions.

X

Build smarter apps with Machine Learning, Bots, Cognitive Services - Start free.

Start Learning Now