The Problem "Concurrency"
When you build a multithreaded application, your program needs to ensure that shared data should be protected from the possibility of multiple thread engagement with its value. What's going to happen if multiple threads were accessing the data at the same point? The CLR can suspend any thread for a while who's going to update the value or is in the middle of updating the value and at same time a thread comes to read that value which is not completely updated, that thread is reading an uncompleted/unstable data.
To illustrate the problem of concurrency let us write some line of code.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("----Synchronnization of Threads-----");
Console.WriteLine("Main Thread {0}", Thread.CurrentThread.ManagedThreadId);
Printer p = new Printer();
Thread[] threads = new Thread[5];
//Queue 5 threads
for (int i = 0; i < 5; i++)
{
threads[i] = new Thread(new ThreadStart(p.PrintNumbersNonSync));
}
foreach (Thread t in threads)
{
t.Start();
}
Console.ReadLine();
}
}
class Printer
{
public void PrintNumbersNonSync()
{
Console.WriteLine(" ");
Console.WriteLine("Executing Thread {0}", Thread.CurrentThread.ManagedThreadId);
for (int i = 1; i <= 10; i++)
{
Console.Write(i + " ");
}
}
}
Run this program multiple times and watch the output:
Output 1:
Output 2:
As you can see all the output will vary each time you run the program.
What is happening here is that all the threads are sharing the same object of the Printer class and trying to execute the same function at the same time so every time the shared data is being updated randomly which is an unstable state.
Synchronization of threads Solution to the problem of concurrency
Use the locks whenever there's a Shared section. C# provides a Lock keyword that can be used to lock the section that is being accessed by multiple threads. This is the very basic technique to avoid the instability in a multithreaded environment while programming with C#.
The Lock keyword requires you to specify the token (an object reference) that must be acquired by a thread to enter within the lock scope. In the case of a private method you can lock it down by passing the reference of the current type using the "this" keyword.
For e.g.
public void PrintNumbersSynchronized()
{
//Synchronization thread
lock (this)
{
Console.WriteLine(" ");
Console.WriteLine("Executing Thread {0}", Thread.CurrentThread.ManagedThreadId);
for (int i = 1; i <= 10; i++)
{
Console.Write(i + " ");
}
}
}
However if you are locking down a region of code within a public member, it is safer (and best practice) to declare a private object member variable to serve as the lock token:
class Printer
{
// Synchronization token
private Object ThreadLock = new object();
public void PrintNumbersSynchronized()
{
//Synchronization thread
lock (ThreadLock)
{
Console.WriteLine(" ");
Console.WriteLine("Executing Thread {0}", Thread.CurrentThread.ManagedThreadId);
for (int i = 1; i <= 10; i++)
{
Console.Write(i + " ");
}
}
}
}
Output:
Other methods to perform the synchronization
Monitor is a class in the System.Threading namespace that also provides the same functionality. Actually lock is just a shorthand notation of Monitor class. When the compiler processes a lock it actually resolves to the following:
public void PrintNumbersSync()
{
Monitor.Enter(ThreadLock);
try
{
Console.WriteLine(" ");
Console.WriteLine("Executing Thread {0}", Thread.CurrentThread.ManagedThreadId);
for (int i = 1; i <= 10; i++)
{
Console.Write(i + " ");
}
}
finally
{
Monitor.Exit(ThreadLock);
}
}
Here with a little more code you can see having the try..finally block. Other than Enter() and Exit() methods, the Monitor class also provides the methods like Monitor.Pulse() and PulseAll() to inform the waiting threads that it has completed.
Simple operations using the Interlocked Type
To perform some basic operation it's not necessary that you write the block using the lock or Monitor. The System.Threading namespace provides an interesting class that can help you out.
Now you can replace this
lock (ThreadLock)
{
intVal++;
}
By
int newVal = Interlocked.Increment(ref intVal);
It returns the new values of the updated variable as well as update the referenced value at the same time.
Similarly,
Add()
Exchange() //for swapping
CompareExchange() // Compare a value and then exchange
Equals()
Decrement()
Read()
Are some basic operations you can perform in a multithreaded environment to make your thread safe using the Interlocked class.
Synchronization using the [Synchronization] attribute
The Synchronization attribute is a member of System.Runtime.Remoting.Contexts namespace. In essence, this class-level attribute effectively locks down all instance member code of the object for thread safety. Additionally you have to derive your class from the ContextBoundObject to keep your object within the contextual boundaries.
Here's the complete code:
class Program
{
static void Main(string[] args)
{
Console.WriteLine("----Synchronnization of Threads-----");
Console.WriteLine("Main Thread {0}", Thread.CurrentThread.ManagedThreadId);
Printer p = new Printer();
Thread[] threads = new Thread[5];
//Queue 5 threads
for (int i = 0; i < 5; i++)
{
threads[i] = new Thread(new ThreadStart(p.PrintNumbersNonSync));
}
foreach (Thread t in threads)
{
t.Start();
}
Console.ReadLine();
}
}
[Synchronization]
class Printer : ContextBoundObject
{
public void PrintNumbersNonSync()
{
Console.WriteLine(" ");
Console.WriteLine("Executing Thread {0}", Thread.CurrentThread.ManagedThreadId);
for (int i = 1; i <= 10; i++)
{
Console.Write(i + " ");
}
}
}
Output:
This approach is a lazy way to write thread safe code because CLR that can lock non-threaded sensitive data and that could be a victim of Over Locking. So please choose this approach wisely and carefully.
Happy Threading…!!