Introduction
You might have already come across this multithreading word or multithreading concept in C# .NET, or in any other programming language.
In my case, I came across the word multithreading many times. When I tried to gain an understanding of it through articles and documents, almost all the articles explained multithreading clearly but many authors couldn’t explain how it is different, in fact, some authors specified it as it is completely asynchronous. But I haven't seen an explanation anywhere about how multithreading, Parallel Programming, and asynchronous are different at least in their own way of doing things and this creates confusion among readers.
Here, in this article, I’ll try to explain the concept in an easy way. I’ll try focusing on what is multithreading, why we use it, and in what type of situations it is most useful. Also, is it different from asynchronous or not, and the structure of multithreading along with some basic examples in order to understand?
Before getting into details, we define, what is a thread and multithreading metaphorically in the real world.
What is Thread?
A Word Editor saves the article automatically from time to time known as Thread. (This means that it's one way of execution).
What is Multi Thread?
While Word Editor is saving the article, Grammarly is working behind to find the mistakes in the sentences is known as Multithread. (Having more than one execution.)
What is Multithreading?
Making use of multi-threads in a program where all are running at the same time but performing different tasks.
What is the use of threading?
Thread helps in improving application performance by doing things in a way of parallel programming.
What is Parallel Programming?
It is the flow of a program where the application is broken into parts and for each part, there will be CPUs that deal at the same time. So, by dividing these parts the overall time taken by an application will be reduced that’s how an application's performance will be increased.
(This means that it's saying Hey! My name is Parallel, I simply distribute my work so that there won't be a burden on me, and all my friends who have taken my work will return it to me at the same time after finishing it and I’ll wait until they do so)
Are Parallel Programming and Asynchronous the same?
Asynchronous is a nonblocking way of doing things, it executes without waiting for an event and carries on with things.
(This means It's saying Hey! I'm asynchronous, please go and do this task for me and come back with results after you have finished, and meanwhile, I’ll do some other work). You can find more about asynchronous here.
The difference is, Parallel mostly depends on Hardware whereas synchronization is about Tasks, and Threading is about workers.
Note. C# supports parallel execution of code with the help of multithreading. (means multithreading is about the parallel way of execution)
Structure of Multithreading
We use the “Thread” Keyword which is a class taken from the System. Threading namespace.
How a Thread is created?
To implement a thread, we need to make use of a threading namespace in our program and this namespace has a Thread class, we use this to create a Thread object and these thread objects create threads in the application.
How Threading Works?
In multithreading, the thread scheduler takes help from the operating system to schedule threads so that it can allocate time for each thread.
In a single Processor machine, only one thread executes at a time, and for a dual-core processor ideally, we can have about 4 threads, and for a quad-core processor, we can create up to 8 threads. Improper management also creates more threads on a single processor machine which may lead to resource bottlenecks.
Example Program on Multithreading
using System;
using System.Threading;
class Program
{
public static void Main()
{
Thread ThreadObject1 = new Thread(Example1); // Creating the Thread
Thread ThreadObject2 = new Thread(Example2);
ThreadObject1.Start(); // Starting the Thread
ThreadObject2.Start();
}
static void Example1()
{
Console.WriteLine("Thread1 Started");
for (int i = 0; i <= 5; i++)
{
Console.WriteLine("Thread1 Executing");
Thread.Sleep(5000); // Sleep is used to pause a thread and 5000 is MilliSeconds that means 5 Seconds
}
}
static void Example2()
{
Console.WriteLine("Thread2 Started");
for (int i = 0; i <= 5; i++)
{
Console.WriteLine("Thread2 Executing");
Thread.Sleep(5000);
}
}
}
Example Program to differentiate MainThread with Worker Thread
What is MainThread?
A Thread that executes first in a process is known as the MainThread.
What does Process mean in Multithreading?
To execute a program, we need some resources provided by this Process. Each process starts with a single thread, called a primary thread. This process also has the ability to create additional threads from the thread it possesses.
What is Synchronization?
Synchronization is a concurrent execution of two or more threads.
Why this synchronization is needed?
Synchronization avoids conflicts between threads, these conflicts arise when they are running in a parallel way trying to modify some variables. Synchronization makes sure that only one process or thread accesses the critical section of the program.
How do we manage Synchronization?
To handle this, we have various methods, divided into 4 categories
- Blocking Methods
- Locking Constructs
- No blocking Synchronization
- Signaling
Here, we try to demonstrate a few methods for understanding.
Join
In the thread, synchronization join is a blocking mechanism that helps in pausing the calling thread. This continues until the thread execution has finished on which the join is called.
Here we try to find the differences between Join() and without Join().
using System;
using System.Threading;
class Program
{
public static void Main()
{
// Creating the WorkerThread with the help of Thread class.
Thread ThreadObject1 = new Thread(WorkerThread);
ThreadObject1.Start(); // Starting the Thread
//ThreadObject1.Join(); // Using Join to block the current Thread
Console.WriteLine("1. MainThread Started");
for (int i = 0; i <= 3; i++)
{
Console.WriteLine("-> MainThread Executing");
Thread.Sleep(3000); // Here 5000 is 5000 Milli Seconds means 5 Seconds
}
// We are calling the Name of Current running Thread using CurrentThread
Thread Th = Thread.CurrentThread;
Th.Name = "Main Thread";
Console.WriteLine("\nGetting the Name of Currently running Thread");
// Name Property is used to get the name of the current Thread
Console.WriteLine($"Current Thread Name is: {Th.Name}");
// Priority Property is used to display the Priority of current Thread
Console.WriteLine($"Current Thread Priority is: {Th.Priority}");
}
static void WorkerThread()
{
Console.WriteLine("2. WorkerThread Started");
for (int i = 0; i <= 3; i++)
{
Console.WriteLine("-> WorkerThread Executing");
Console.WriteLine("Child Thread Paused");
// Sleep method is used to pause the Thread for a specific period
Thread.Sleep(3000);
Console.WriteLine("Child Thread Resumed");
}
}
}
Output Using Join
Output Without using Join
In the above program in Main() to understand clearly, the threads that are created using Thread Class are called child Threads, you can see, that using this Thread Class we created Worker Thread, so WorkerThread is known to be a Child Thread, and the Main() is known to be the Parent.
Lock
The lock helps lock the current Thread so that no other thread can interrupt the execution. Unlocking happens after the execution finishes.
Example Code for Lock Synchronization Method
using System;
using System.Threading;
namespace LockDemo
{
class LockExample1
{
// Creating a normal Method to Display Names
public void Display()
{
// Lock is used to lock in the Current Thread
lock (this)
{
for (int i = 0; i <= 3; i++)
{
Thread.Sleep(3000);
Console.WriteLine($"My Name is Abhishek{i}");
}
}
}
}
class Example2
{
public static void Main(string[] args)
{
// Creating object for LockExample1 Class as _locker so that we can access its Display Method
LockExample1 _locker = new LockExample1();
Console.WriteLine("Threading with the help of Lock");
// Calling the Display Method using ThreadStart Delegate which is supplied to Thread constructor.
Thread t1 = new Thread(new ThreadStart(_locker.Display));
Thread t2 = new Thread(new ThreadStart(_locker.Display));
t1.Start(); // Starting Thread1
t2.Start(); // Starting Thread2
}
}
}
Output Threading With Lock
Output Threading Without Lock
In the above program, we used a normal Display method with Lock() to lock the thread. To Display these names we have to use a ThreadStart which is a Delegate that represents a method to be invoked when this thread begins executing.
Deadlocks
In Deadlock, the two threads each wait for the resource that is needed but these resources are held by each other making both of them not proceed further. (This means It's saying Hey! I'm Deadlock, my friends Lock1 and Lock2 are not releasing their resources and they couldn't understand each other. Lock1 has Lock2 resources and Lock2 has Lock1 resources and they created confusion, so I have no option but to stop this situation of Deadlock).
Example Program for Deadlocks
using System;
using System.Threading;
namespace DeadlocksDemo
{
public class Locking
{
static readonly object Lock1 = new object();
static readonly object Lock2 = new object();
static void Display()
{
Console.WriteLine("Trying to lock Locker1");
lock (Lock1)
{
Console.WriteLine("Locked Locker1");
Thread.Sleep(1000);
Console.WriteLine("Locking Locker2");
lock (Lock2)
{
Console.WriteLine("Locked Locker2");
}
Console.WriteLine("Released Locker2");
}
Console.WriteLine("Released Locker1");
}
static void Main()
{
new Thread(new ThreadStart(Display)).Start();
Thread.Sleep(1000);
Console.WriteLine("Trying to lock Locker2");
lock (Lock2)
{
Console.WriteLine("Locked Locker2");
Console.WriteLine("Locking Locker1");
lock (Lock1)
{
Console.WriteLine("Locked Locker1");
}
Console.WriteLine("Released Locker1");
}
Console.WriteLine("Released Locker2");
Console.Read();
}
}
}
Output
In the above program, the Lock inside both methods is waiting for each other to finish. For both to achieve their goals they need to release, but they are not doing so. As both couldn’t achieve what they were expecting, this type of accessing only makes threads freeze and is ultimately prone to Deadlocks.
Conclusion
I hope that you got an idea of the threading concept in C# by knowing all its real definitions and its relations with other methods. But there is more to learn because multithreading is a huge concept in C# with various methods and workarounds. To know more about Threading in C#, this page has in-depth information here.
Happy Coding!