-
Thread: The path of execution of instructions within a process is called a thread. A program with multiple simultaneous paths of execution is said to be multithreaded application.
-
A running instance of an application is a process. Every process has its own memory address space and it is in that all the data and the instructions of that process reside.
Types of Scheduling:
Preemptive: OS can un-schedule a thread even if it is not completed its task.
Non–Preemptive: OS cannot unschedule the thread when it is not completed. A Thread should either unschedule itself of should complete its task.
-
Scheduling is done based on the priority of the thread. A thread with highest priority is executed in the processor. If multiple threads with exist same priority then a time slice is assigned to each thread.
-
In MS-Windows execution of the thread is based on: Preemptive Scheduling Priority Time slice
-
Also, in all versions of MS Windows, to avoid starvation low priority threads are also executed in the processor but the number of times they execute is very less.
-
Running: A thread is said to be in running state when it is in processor executing one of its instruction.
-
Ready: Thread is waiting for processor to be allocated to it. This is the only entry point to running state. When the tread is unscheduled it returns from running to ready state.
-
Dead: After the execution of all the instructions in the thread it goes to dead state. Dead threads cannot be revived.
-
Sleeping: While sleeping the thread doesn’t perform any task. A thread goes to sleeping state by itself and for a predefined time. A sleeping thread when interrupted throws “ThreadInterruptedException”. All the resources/locks that the thread is holding are blocked when a thread is in sleeping state.
-
Suspended: A thread can suspend itself or by another thread. The suspended thread cannot resume by itself. A thread can go to suspended state for an indefinite time.
-
Blocked: A thread is said to be in blocked state when the resources are not available to it and it is waiting for them. The thread automatically resumes once the resource is made available.
-
Waiting: The thread before it goes to waiting state releases its resources / locks it has blocked. A waiting thread has to be pulsed by another thread only then it can resume and goes to ready state.
In .Net every thread has two objects associated with it.
-
An Object of type System.Threading.Threads and it is responsible for managing the lifetime and the states of the thread.
-
A custom object, it is responsible for providing the instructions and data which the thread has to manage during its lifetime.
Example 1:
using System; using System.Threading; class Demo { public string Message; public int Interval = 1000; public void Run() { for (int i = 0; i < 5; i++) //while(true) { Console.WriteLine(Thread.CurrentThread.Name + ” : ” + Message); Thread.Sleep(Interval); } } }
|
class Program { static void Main(string[] args) { Console.WriteLine(“Main method begins”); Demo d = new Demo(); Thread t = new Thread(new ThreadStart(d.Run)); t.Name = “T”; d.Message = “Hi”; d.Interval = 1000; t.Start(); Demo d1 = new Demo(); Thread t1 = new Thread(new ThreadStart(d1.Run)); t1.Name = “T1”; d1.Message = “Hello”; d1.Interval = 2000; t1.Start(); t.Join(2000); if (t.IsAlive) Console.WriteLine(“t is alive”); t1.Join(); //t1.IsBackground = t.IsBackground = true; Console.WriteLine(“Main method ends”); } } |
t.Join():If a thread executes t.Join() the current thread state is changed to waiting and it remains in that state until the thread referred by “t” is terminated.
Join(3000) – The current threads waits for a max of 3000 milliseconds and then would resume automatically.
t.IsAlive() to check if the thread is dead or alive..
Thread.CurrentThread : To Get the reference of the current thread.
t.ThreadPriority = ThreadPriority.Highest.
t.Abort(): To stop the thread. When the Abort() method is called on a thread it throws ThreadAbortException irrespective of its state. If this exception is unhandled the thread terminates, and if it is handled and if Thread.ResetAbort() is executed the thread is not aborted and would continue normal execution.
t.IsBackground: When a thread is created as background thread, the creator thread does not wait for the background thread to terminate or join the creator thread. The background thread is automatically aborted when the creator thread aborts.
Example 2: Program to Print “Data” at a regular input and also facility to change that data by reading its value from keyboard.
class PrintThread { public string Data; public void Run() { while (true) { Console.WriteLine(Data); Thread.Sleep(1000); } } } |
class PrintProgram { public static void Main() { PrintThread p = new PrintThread(); Thread t = new Thread(new ThreadStart(p.Run)); t.Start(); while (true) { Console.WriteLine(“Please enter the data: “); p.Data = Console.ReadLine(); } } } |
Example 3: To Demonstrate the Priority of Thread
In windows OS for every cycle of high priority thread scheduled in the processor, the priority of low priority thread is boosted by one unit. This is done so that even those threads whose priority is low are given a chance to execute in the processor to avoid starvation.
Priority Demo class Hello { public long Counter; public void Run() { while(true) Counter++; } } Note: On Dual Core processor machine, this program should be executed with three threads because the processor would execute two threads at the same time irrespective of their priority. |
class PriorityProgram { static void Main(string[] args) { Hello h1 = new Hello(); Thread t1 = new Thread(new ThreadStart(h1.Run)); Hello h2 = new Hello(); Thread t2 = new Thread(new ThreadStart(h2.Run)); t1.Priority = ThreadPriority.AboveNormal; t2.Priority = ThreadPriority.BelowNormal; Thread.CurrentThread.Priority = ThreadPriority.Highest; t1.Start(); t2.Start(); Thread.Sleep(2000); t1.Abort(); t2.Abort(); double perT1 = 100.0 * h1.Counter / (h1.Counter + h2.Counter); double perT2 = 100.0 * h2.Counter / (h1.Counter + h2.Counter); Console.WriteLine(“T1 ” + perT1); Console.WriteLine(“T2 ” + perT2); } } |
Suspend-Resume / Sleep – Interrupt / Abort Demo
Add to the project a new Windows Form: (DemoForm)
private void Run() { int n = 0; while (true) { try { n++; lblCounter.Text = n.ToString(); Thread.Sleep(1000); } catch (ThreadAbortException e) { DialogResult dlgResult; dlgResult = MessageBox.Show(“Are you sure?”, “Abort”, MessageBoxButtons.YesNo); if (dlgResult == DialogResult.No) Thread.ResetAbort(); } catch (ThreadInterruptedException e) { n = 0; } } } |
Thread t; private void btnStart_Click(. . .) { t = new Thread(new ThreadStart(Run)); t.IsBackground = true; t.Start(); } private void btnAbort_Click(. . .) { t.Abort(); } private void btnSuspend_Click(. . .) { t.Suspend(); } private void btnResume_Click(. . .) { t.Resume(); } private void btnInterrupt_Click(. . .) { t.Interrupt(); } private void btnThreadState_Click(. . .) { MessageBox.Show(t.ThreadState.ToString()); } //To make form as the startup object. public static void Main() { Application.Run(new DemoForm()); } |
In Project Properties: Change Output Type to Windows Application and Startup Object to DemoForm
Thread Synchronization
Requirement: In situation where one object is shared by more than one thread, if the thread has to modify the state of the object it should ensured that only one thread does so at a given instance of time.
Critical Section is a block of code which can be executed by only one thread at any given instance of time.
If there are two threads executing on same shared object then both the threads can execute some method on the object at the same point of time and if they then change the state of the object, it may result in ambiguity of data causing Threads De-Synchronization. To avoid this we use lock block.
In C# lock(ob) { } |
In VB SyncLock (ob) End SyncLock |
Every object in .NET has a special type of resource called as Monitor. A thread trying to enter the lock block would have to acquire the monitor of the specified object and only if it can do so it would be executing the locked block other wise it will have to wait for the monitor. |
Example
class Shared { int n; public int Incr() { lock (this) { n++; int k = 0; for (long i = 0; i < 100000000; i++) k++; return n; } } } |
class Demo { Shared s; public Demo(Shared s) { this.s = s; } public void Run() { //lock (s) //To be used if the Demo.Incr is not coded //{ for Thread Safety (not having lock block) Console.WriteLine(s.Incr()); //} } } |
class SyncDemo { public static void Main() { Shared s = new Shared(); Demo d1 = new Demo(s); Demo d2 = new Demo(s); Thread t1 = new Thread(new ThreadStart(d1.Run)); Thread t2 = new Thread(new ThreadStart(d2.Run)); t1.Start();t2.Start(); } } |
|
Mutex
It is a synchronization resource managed by the OS. Thus it can be used for synchronizing threads running in different processes.
Note: Monitor is a .Net specific object and is local to a given process and thus it cannot be used for synchronizing threads running in different processes.
If two or more threads have to be synchronized using Mutex, then either they should all refer to the same Mutex object or if the objects are different then all the Mutex objects must have same name so that all the mutex objects refers to the same mutex resource in the OS.
WaitOne(): Check for the availability of mutex. If available acquires it and would continue execution other the current thread state is changed to waiting for mutex.
ReleseMutex() – Releases the mutex
Mutex m = new Mutex(false, “test”);
If the first parameter is true, the first thread creating the mutex object will be the owner of the mutex. False will prevent the thread object to acquire the mutex till WaitOne() method is executed.
“test” is the name of the mutex as identified in OS.
Example 1 FileStream fs; StreamWriter sw; Mutex m = new Mutex(false, “M1”); private void btnOpenFile_Click(object sender, EventArgs e) { bool flag = m.WaitOne(0,false); if (!flag) { MessageBox.Show(“File is already being used by another app”); return; } fs = new FileStream(“c:\\mutexdemo.txt”, FileMode.Create); sw = new StreamWriter(fs); btnOpenFile.Enabled = false; btnCloseFile.Enabled = true; btnWriteToFile.Enabled = true; } private void btnCloseFile_Click(object sender, EventArgs e) { sw.Close(); fs.Close(); btnOpenFile.Enabled = true; btnCloseFile.Enabled = false; btnWriteToFile.Enabled = false; m.ReleaseMutex(); } private void btnWriteToFile_Click(object sender, EventArgs e) { sw.Write(txtText.Text); txtText.Text = “”; } public static void Main() { Application.Run(new MutexDemoForm()); } |
Example 2: To allow only once instance of the application to run at a time. public static void Main() { Mutex m = new Mutex(false, “M1”); bool firstInstance = m.WaitOne(0,true); if (firstInstance) { Application.Run(new MainForm()); m.ReleaseMutex(); } else MessageBox.Show(“Instance already running”); } Note: Use Ctrl + F5 for executing the Program Two times |
Semaphore
Semaphore is used for synchronizing threads (within and in different process) and it works exactly like Mutex but has a facility to allow more than one thread (but a pre defined count) to execute a given block at the same time.
Mutex can be treated as a special case of semaphore where count = 1.
class Demo1 { //static Semaphore s = new Semaphore(3,3); Semaphore s = new Semaphore(3,3, “Test”); public void Run() { Console.WriteLine(Thread.CurrentThread.Name + ” Started”); s.WaitOne(); Thread.Sleep(1000); s.Release(); Console.WriteLine(Thread.CurrentThread.Name + ” Ended”); } } |
class SemaphoreDemo { public static void Main() { for (int i = 0; i < 15; i++) { Demo1 d = new Demo1(); Thread t = new Thread( new ThreadStart(d.Run)); t.Name = “T” + i; t.Start(); } Console.WriteLine(“Main method ends”); } } |