Synchronization in Threads
When we have multiple threads that share data, we need to provide synchronized access to the data. We have to deal with synchronization issues related to concurrent access to variables and objects accessible by multiple threads at the same time. This is controlled by giving one thread a chance to acquire a lock on the shared resource at a time. We can think it like a box where the object is available and only one thread can enter into and the other thread is waiting outside the box until the previous one comes out.
Example
using System; using System.Threading; namespace ConsoleApplication1 { class Program { static void Main(string[] arg) { Console.WriteLine("*****Multiple Threads*****"); Printer p = new Printer(); Thread[] Threads = new Thread[3]; for (int i = 0; i < 3; i++) { Threads[i] = new Thread(new ThreadStart(p.PrintNumbers)); Threads[i].Name = "Child " + i; } foreach (Thread t in Threads) t.Start(); Console.ReadLine(); } } class Printer { public void PrintNumbers() { for (int i = 0; i < 5; i++) { Thread.Sleep(100); Console.Write(i + ","); } Console.WriteLine(); } } }
In the above example, we have created three threads in the main method and all the threads are trying to use the PrintNumbers() method of the same Printer object to print to the console.
Now we can see, as the thread scheduler is swapping threads in the background each thread is telling the Printer to print the numerical data. We are getting inconsistent output as the access of these threads to the Printer object is synchronized. There are various synchronization options which we can use in our programs to enable synchronization of the shared resource among multiple threads.
lock (objecttobelocked) { objecttobelocked.somemethod(); }
Here objecttobelocked is the object reference which is used by more than one thread to call the method on that object. The lock keyword requires us to specify a token (an object reference) that must be acquired by a thread to enter within the lock scope. When we are attempting to lock down an instance level method, we can simply pass the reference to that instance. (We can use this keyword to lock the current object) Once the thread enters into a lock scope, the lock token (object reference) is inaccessible by other threads until the lock is released or the lock scope has exited.
If we want to lock down the code in a static method, we need to provide the System.Type of the respective class.
Converting the Code to Enable Synchronization using the Lock Keyword
public void PrintNumbers() { lock (this) { for (int i = 0; i < 5; i++) { Thread.Sleep(100); Console.Write(i + ","); } Console.WriteLine(); } }
Monitor.Enter() method is the ultimate recipient of the thread token. We need to write all code of the lock scope inside a try block. The finally clause ensures that the thread token is released(using the Monitor.Exit() method), regardless of any runtime exception.