Multithreading in Detail

Introduction

The term multithreaded programming may sound complicated, but it is quite easy to do in C#. This article explains how multithreading works on your typical, general purpose computer. You will learn how the operating system manages thread execution and learn how to manipulate the Thread class in your program to create and start managed threads. This article also renders the information you need to know when programming with multiple threads, such as the Thread class, pools, threading issues and BackgroundWorker.

Multithreading Overview

A thread is an independent stream of instructions in a program. Each written program is a paradigm of sequential programming in which there is a beginning, an end, a sequence and a single point of execution. A thread is similar to a sequential program. However, a thread itself is not a program, it can't run its own, but run within a program.

The real concern of threads is not about a single sequential thread, but rather the use of multiple threads in a single program. All running at the same time and performing multiple tasks. This mechanism is referred to as Multithreading. A thread is considered to be a lightweight process because it runs within the context of a program and takes advantage of resources allocated for that program.

process

Threads are important, both for client and server applications. While in C# program coding, when you type something into an editor, the dynamic help (intellisense) windows immediately shows the relevant topics that fit the code. One thread is waiting for input from the user, while another does some background processing. A third thread can store the written data in a temporary file, while another one downloads a file from a specific location.

With the task manager, you can turn on the Thread column and see the processes and the number of threads for every process. Here, you can see that only cmd.exe is running inside a single thread while all other applications use multiple threads.

task manager processes

The operating system schedules threads. A thread has a priority and every thread has its own stack, but the memory for the program code and heap are shared among all threads of a single process.

A process consists of one or more threads of execution that is simply referred to as threads. A process always consists of at least one thread called the main thread (the Main method in a C# program). A single thread process contains only one thread whereas a multithread process can contain more than one thread of execution.

memory

On a computer, the operating system loads and starts applications. Each application or service runs as a separate process on the machine. The following image illustrates that there are quite a few processes actually running than there are applications. Many of the processes are background operating system processes that are started automatically when the computer powers up.

applications

System.Threading Namespace

Under the .NET platform, the System.Threading namespace provides a number of types that enable the direct construction of a multithreaded application.

Type Description
Thread It represents a thread that execute within the CLR. Using this, we can produce additional threads in application domain.
Mutex It is used for synchronization between application domains.
Monitor It implements synchronization of objects using Locks and Wait.
Smaphore It allows limiting the number of threads that can access a resource concurrently.
Interlock It provides atomic operations for variables that are shared by multiple threads.
ThreadPool It allows you to interact with the CLR maintained thread pool.
ThreadPriority This represents the priority level such as High, Normal, Low.

System.Threading.Thread class

The Thread class allows you to create and managed the execution of managed threads in your program. They are called a managed thread because you can directly manipulate each thread you create. You will find the Thread class along with useful stuff in the System.Threading namespace.

Member Type Description
CurrentThread Static Return a reference of current running thread.
Sleep Static Suspend the current thread for a specific duration.
GetDoamin Static Return a reference of current application domain.
Currentcontext Static Return a reference of current context in which the thread currently running.
Priority Instance Level Get or Set the Thread priority level.
IsAlive Instance Level Get the thread state in form of True or False value.
Start Instance Level Instruct the CLR to start the thread.
Suspend Instance Level Suspend the thread.
Resume Instance Level Resume a previously suspended thread.
Abort Instance Level Instruct the CLR to terminate the thread.
Name Instance Level Allows establishing a name to thread.
IsBackground Instance Level Indicate whether a thread is running in background or not.

Multithreading Implementation

The following section plays with the numerous System.Threading namespace static and instance-level members and properties.

Obtaining Current Thread Information

To illustrate the basic use of the Thread type, suppose you have a console application in which the CurrentThread property retrieves a Thread object that represents the currently executing thread.

  1. using System;  
  2. using System.Threading;  
  3.   
  4. namespace threading  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             Console.WriteLine("**********Current Thread Informations***************\n");  
  11.             Thread t = Thread.CurrentThread;  
  12.             t.Name = "Primary_Thread";  
  13.   
  14.             Console.WriteLine("Thread Name: {0}", t.Name);  
  15.             Console.WriteLine("Thread Status: {0}", t.IsAlive);  
  16.             Console.WriteLine("Priority: {0}", t.Priority);  
  17.             Console.WriteLine("Context ID: {0}", Thread.CurrentContext.ContextID);  
  18.             Console.WriteLine("Current application domain: {0}",Thread.GetDomain().FriendlyName);  
  19.               
  20.             Console.ReadKey();    
  21.         }  
  22.                   
  23.     }  
  24. }  

After compiling this application, the output will be as in the following:



Simple Thread Creation

The following simple example explains the Thread class implementation in which the constructor of the Thread class accepts a delegate parameter. After the Thread class object is created, you can start the thread with the Start() method as in the following:

  1. using System;  
  2. using System.Threading;  
  3.   
  4. namespace threading  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             Thread t = new Thread(myFun);  
  11.             t.Start();   
  12.   
  13.             Console.WriteLine("Main thread Running");  
  14.             Console.ReadKey();    
  15.         }  
  16.   
  17.         static void myFun()  
  18.         {  
  19.             Console.WriteLine("Running other Thread");    
  20.         }  
  21.     }  
  22. }  

 

After running the application, you get the following output of the two threads as in the following:

thread running

The important point to be noted here is that there is no guarantee of what output comes first, in other words which thread starts first. Threads are scheduled by the operating system. So which thread comes first can be different each time.

Background Thread

The process of the application keeps running as long as at least one foreground thread is running. If more than one foreground thread is running and the Main() method ends, the process of the application keeps active until all foreground threads finish their work.

When you create a thread with the Thread class, you can define it as either a foreground or background thread by setting the property IsBackground. The Main() method set this property of the thread t to false. After setting the new thread, the main thread just writes an end message to the console. The new thread writes a start and an end message and in between it sleeps for 2 seconds.

  1. using System;  
  2. using System.Threading;  
  3.   
  4. namespace threading  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             Thread t = new Thread(myFun);  
  11.             t.Name = "Thread1";  
  12.             t.IsBackground = false;    
  13.             t.Start();   
  14.             Console.WriteLine("Main thread Running");  
  15.             Console.ReadKey();    
  16.         }  
  17.   
  18.         static void myFun()  
  19.         {  
  20.             Console.WriteLine("Thread {0} started", Thread.CurrentThread.Name);  
  21.             Thread.Sleep(2000);    
  22.             Console.WriteLine("Thread {0} completed", Thread.CurrentThread.Name);    
  23.         }  
  24.     }  
  25. }  
When you compile this application, you will still see the completion message written to the console because the new thread is a foreground thread. Here, the output is as in the following:

thread1 completed

If you change the IsBackground property to start the new thread to true, the result shown in the console is different as in the following:

thread1

Concurrency issues

Programming with multiple threads is not an easy task. When starting multiple threads that access the same data, you can get an intermediate problem that is hard to resolve. When you build multithreaded applications, the program needs to ensure that any piece of shared data is protected against the possibility of numerous threads changing its value.

Race Condition

A race condition can occur if two or more threads access the same objects and access to the shared state is not synchronized. To illustrate the problem of a Race condition, let's build a console application. This application uses the Test class to print 10 numbers by pausing the current thread for a random number of times.

  1. Using System;  
  2. using System.Threading;  
  3.   
  4. namespace threading  
  5. {  
  6.     public class Test  
  7.     {  
  8.         public void Calculation()  
  9.         {     
  10.             for (int i = 0; i < 10; i++)  
  11.             {  
  12.                 Thread.Sleep(new Random().Next(5));  
  13.                 Console.Write(" {0},", i);  
  14.             }  
  15.             Console.WriteLine();  
  16.         }  
  17.     }  
  18.     class Program  
  19.     {  
  20.         static void Main(string[] args)  
  21.         {  
  22.             Test t = new Test();  
  23.             Thread[] tr = new Thread[5];  
  24.   
  25.             for (int i = 0; i < 5; i++)  
  26.             {  
  27.                 tr[i] = new Thread(new ThreadStart(t.Calculation));  
  28.                 tr[i].Name = String.Format("Working Thread: {0}", i);  
  29.             }  
  30.   
  31.             //Start each thread  
  32.             foreach (Thread x in tr)  
  33.             {  
  34.                 x.Start();  
  35.             }  
  36.             Console.ReadKey();  
  37.         }          
  38.     }  
  39. }  

After compiling this program, the primary thread within this application domain begins by producing five secondary threads. And each working thread is told to call the Calculate method on the same Test class instance. So you have taken no precaution to lock down this object's shared resources. Hence, all five threads start to access the Calculation method simultaneously. This is the Race Condition and the application produces unpredictable output as in the following:

unpredictable output

Deadlocks

Having too much locking in an application can also get your application into trouble. In a deadlock, at least two threads wait for each other to release a lock. As both threads wait for each other, a deadlock situation occurs and the threads wait endlessly and your computer eventually hangs.

Here, both of the methods changed the state of the two objects obj1 and obj2 by locking them. The methods DeadLock1() first locks obj1 and next locks obj2. The method DeadLock2() first locks obj2 and then obj1. So the lock for obj1 is resolved then a thread switch occurs and the second method starts to run and gets the lock for obj2. The second thread now waits for the lock of obj1. Both threads now wait and don't release each other. This is a typical deadlock.

  1. using System;  
  2. using System.Threading;  
  3.   
  4. namespace threading  
  5. {      
  6.     class Program  
  7.     {  
  8.         static object obj1 = new object();  
  9.         static object obj2 = new object();  
  10.   
  11.         public static void DeadLock1()  
  12.         {  
  13.             lock (obj1)  
  14.             {  
  15.                 Console.WriteLine("Thread 1 got locked");  
  16.                 Thread.Sleep(500);   
  17.                 lock (obj2)  
  18.                 {  
  19.                     Console.WriteLine("Thread 2 got locked");  
  20.                 }  
  21.             }  
  22.         }  
  23.   
  24.         public static void DeadLock2()  
  25.         {  
  26.             lock (obj2)  
  27.             {  
  28.                 Console.WriteLine("Thread 2 got locked");  
  29.                 Thread.Sleep(500);   
  30.                 lock (obj1)  
  31.                 {  
  32.                     Console.WriteLine("Thread 1 got locked");  
  33.                 }  
  34.             }  
  35.         }  
  36.    
  37.         static void Main(string[] args)  
  38.         {  
  39.             Thread t1 = new Thread(new ThreadStart(DeadLock1));  
  40.             Thread t2 = new Thread(new ThreadStart(DeadLock2));  
  41.   
  42.             t1.Start();  
  43.             t2.Start();  
  44.   
  45.            Console.ReadKey();  
  46.         }  
  47.     }  
  48. }  
Synchronization

Problems that can happen with multiple threads such as Race condition and deadlocks can be avoided by Synchronization. It is always suggested to avoid concurrency issues by not sharing data between threads. Of course, this is not always possible. If data sharing irresistible, you must use synchronization so that only one thread at a time accesses and changes shared states. This section discusses various synchronization technologies.

Locks

We can synchronize access of shared resources using the lock keyword. By doing so, incoming threads cannot interrupt the current thread, preventing it from finishing its work. The lock keyword required an object reference.

By taking the previous Race Condition problem, we can refine this program by implementing lock on crucial statements to make it foolproof from race conditions as in the following:

  1. public class Test  
  2.     {  
  3.         public object tLock = new object();  
  4.    
  5.         public void Calculation()  
  6.         {  
  7.             lock (tLock)  
  8.             {  
  9.                 Console.Write(" {0} is Executing",Thread.CurrentThread.Name);  
  10.   
  11.                 for (int i = 0; i < 10; i++)  
  12.                 {  
  13.                     Thread.Sleep(new Random().Next(5));  
  14.                     Console.Write(" {0},", i);  
  15.                 }  
  16.                 Console.WriteLine();  
  17.             }  
  18.         }  
  19.     }  
After compiling this program, this time it produced the desired result as in the following. Here, each thread has sufficed opportunity to finish its tasks.

working thread

Monitor

The lock statement is resolved by the compiler to the use of the Monitor class. The Monitor class is nearly similar to locks but has a big advantage compared to the lock statements in terms of control. You are able to instruct the active thread to wait for some duration time and inform waiting threads when the current thread is completed. Once processed by C# compiler, a lock scope resolves to the following code.

  1. object tLock = new object();  
  2. public void Calculation()  
  3. {  
  4.     Monitor.Enter(tLock);  
  5.     try  
  6.     {  
  7.       for (int i = 0; i < 10; i++)  
  8.       {  
  9.         Thread.Sleep(new Random().Next(5));  
  10.         Console.Write(" {0},", i);  
  11.       }  
  12.     }  
  13.     catch{}  
  14.     finally  
  15.     {  
  16.       Monitor.Exit(tLock);  
  17.     }  
  18.     Console.WriteLine();  
  19. }  
If you see the IL code of the Lock application using ILDASM, you will found the Monitor class reference over there as in the following:

thread monitor

Using [Synchronization] Attribute

The [Synchronization] attribute is a member of System.Runtime.Remoting.Context namespace. This class level attribute effectively locks down all instance of the object for thread safety.

  1. using System.Threading;  
  2. using System.Runtime.Remoting.Contexts;    
  3.   
  4.     [Synchronization]  
  5.     public class Test:ContextBoundObject   
  6.     {  
  7.   
  8.         public void Calculation()  
  9.         {  
  10.   
  11.             for (int i = 0; i < 10; i++)  
  12.             {  
  13.                 Thread.Sleep(new Random().Next(5));  
  14.                 Console.Write(" {0},", i);  
  15.             }  
  16.   
  17.             Console.WriteLine();  
  18.         }  
  19.     }  
Mutex

Mutex stand for Mutual Exclusion is method that offers synchronization across multiple threads. The Mutex calss derive from WaitHandle, you can do a WaitOne() to acquire the mutex lock and be the owner of the mutex that time. The mutex is released by invoking the ReleaseMutex() method as in the following.

  1. using System;  
  2. using System.Threading;  
  3.   
  4. namespace threading  
  5. {      
  6.     class Program  
  7.     {  
  8.         private static Mutex mutex = new Mutex();   
  9.    
  10.         static void Main(string[] args)  
  11.         {  
  12.             for (int i = 0; i < 4; i++)  
  13.             {  
  14.                 Thread t = new Thread(new ThreadStart(MutexDemo));  
  15.                 t.Name = string.Format("Thread {0} :", i+1);  
  16.                 t.Start();   
  17.             }  
  18.             Console.ReadKey();  
  19.         }  
  20.   
  21.         static void MutexDemo()  
  22.         {  
  23.             try  
  24.             {  
  25.                 mutex.WaitOne();   // Wait until it is safe to enter.  
  26.                 Console.WriteLine("{0} has entered in the Domain",  
  27.                     Thread.CurrentThread.Name);  
  28.   
  29.                 Thread.Sleep(1000);    // Wait until it is safe to enter.  
  30.                 Console.WriteLine("{0} is leaving the Domain\r\n",  
  31.                     Thread.CurrentThread.Name);  
  32.   
  33.             }  
  34.             finally  
  35.             {  
  36.                 mutex.ReleaseMutex();  
  37.             }  
  38.         }  
  39.     }  
  40. }  
Once you successfully compile this program, it shows up that each newly created first entered into its application domain. Once, it finished its tasks then it released and second thread started and so on.

thread2

Semaphore

A semaphore is very similar to Mutex but semaphore can be used by multiple threads at once whereas Mutex can't. With a Semaphore, you can define a count how many threads are allowed to access the resources shielded by semaphore simultaneously.
Here in the following example, 5 threads are created and 2 semaphore. In the constructor of semaphore class, you can define no of locks that can be acquired with a semaphore.

  1. using System;  
  2. using System.Threading;  
  3.   
  4. namespace threading  
  5. {  
  6.       
  7.     class Program  
  8.     {  
  9.         static Semaphore obj = new Semaphore(2, 4);  
  10.    
  11.         static void Main(string[] args)  
  12.         {  
  13.             for (int i = 1; i <= 5; i++)  
  14.             {  
  15.                 new Thread(SempStart).Start(i);  
  16.             }  
  17.               
  18.             Console.ReadKey();  
  19.         }  
  20.   
  21.         static void SempStart(object id)  
  22.         {  
  23.             Console.WriteLine(id + "-->>Wants to Get Enter");  
  24.             try  
  25.             {  
  26.                 obj.WaitOne();  
  27.                 Console.WriteLine(" Success: " + id + " is in!");                            
  28.   
  29.                 Thread.Sleep(2000);                
  30.                 Console.WriteLine(id + "<<-- is Evacuating");         
  31.             }  
  32.             finally  
  33.             {  
  34.                 obj.Release();  
  35.                   
  36.             }  
  37.         }  
  38.     }  
  39. }  
When we run this application, 2 semaphores are immediately created and rest of wait because we have create 5 thread. So 3 are in waiting state. The moment, any one of thread released the rest of created one by one as in the following.

Summary

This article explained how to code applications that utilize multiple threads using the System.Threading Namespace. Using multithreading in your application can cause concurrency issues such as Race condition and deadlocks. Finally this article discusses the various ways of synchronization such as Locks, Mutex and Semaphore to handle concurrency problems in which you can protect thread sensitive block of code to ensure that shared resources do not become unusual.

Up Next
    Ebook Download
    View all
    Learn
    View all