About
In this code snippet, we will see some more ways of doing thread synchronization in C#. We’ll look at mutex, semaphore, and thread signaling(ManualResetEvent/AutoResetEvent).
You can find out what thread synchronization is and why it’s needed in this post. Here I will only show a few more ways of doing it.
Mutex
Mutex(mutual exclusion) is very similar to lock/Monitor. The difference is that it can work across multiple processes.
Semaphore
A semaphore is very similar to lock/Monitor. The difference is that it can allow for a specified amount of threads to work with the same resources at the same time. The way it works is the Semaphore class has an integer variable. When the class is instantiated the variable is initialized to the max number of threads that will be allowed to access a resource. When the value of this variable is 0 no more threads are allowed to access the resources being locked. When a thread enters a lock(WaitOne()) it will decrement the said variable by one and when it exits the lock(Release()) it will increase it by one.
Thread Signaling using Auto/ManualResetEvent
Thread signaling is yet another way of doing thread synchronization. When the child thread hits the WaitOne() method it will wait there until the main thread signals it to continue by using the set() method.
Note:
For more information and other thread synchronization methods see Microsoft documentation here. Sometimes you can also avoid having to deal with thread synchronization by using thread-safe classes. So make sure you consult the documentation to see if the class you are using is thread-safe(or if you can find a thread-safe alternative). You might just save yourself some work.
Let’s have a look at the code below to see how to do thread synchronization.
Mutex Code:
using System; using System.Threading; namespace MutexExample { class Program { static void Main(string[] args) { for (int i = 0; i < 5; i++) { Thread doWorkThread = new Thread(new ParameterizedThreadStart(doWork)); doWorkThread.Start(i); } Console.ReadLine(); } //Mutex(mutual exclusion) is very similar to lock/Monitor. The difference is that it can work across multiple processes. //Make mutex. private static Mutex mutex = new Mutex(); public static void doWork(object threadNumber) { try { //The code will stop executing here and wait until the lock is released by the previous thread. mutex.WaitOne(); Console.WriteLine("Thread " + threadNumber.ToString() + " has started."); //Simulate doing time consuming work. Thread.Sleep(1500); Console.WriteLine("Thread " + threadNumber.ToString() + " is done."); } finally { //Realesase the lock so other threads can now get access. mutex.ReleaseMutex(); } } } }
Resulting output:
Semaphore Code:
using System; using System.Threading; namespace SemaphoreExample { class Program { static void Main(string[] args) { for (int i = 0; i < 5; i++) { //Make new thread. Thread doWorkThread = new Thread(new ParameterizedThreadStart(doWork)); //Start thread. doWorkThread.Start(i); } Console.ReadLine(); } //A semaphore is very similar to lock/Monitor. The difference is that it can allow for a specified amount of threads to work with the same resources at the same time. //Make a semaphore that allows for up to two threads to work with the same resource. public static Semaphore semaphore = new Semaphore(2, 2); //Semaphore(initial thread count, max thread count) public static void doWork(object threadNumber) { try { //The code will stop executing here and wait until the lock is released by the previous thread. semaphore.WaitOne(); Console.WriteLine("Thread " + threadNumber.ToString() + " has started."); //Simulate doing time consuming work. Thread.Sleep(1500); Console.WriteLine("Thread " + threadNumber.ToString() + " is done."); } finally { //Realesase the lock so other threads can now get access. semaphore.Release(); } } } }
Resulting output:
Thread Signaling Code:
using System; using System.Threading; namespace ThreadSignaling { class Program { static void Main(string[] args) { //Make new thread. Thread doWorkThread = new Thread(new ParameterizedThreadStart(doWork)); //Start thread. doWorkThread.Start(1); //Do some work. Console.WriteLine("Doing some stuff..."); //Simulate doing time consuming work. Thread.Sleep(2000); //Signal thread to continue. signal.Set(); //Do some work. Console.WriteLine("Doing some more stuff..."); //Simulate doing time consuming work. Thread.Sleep(2000); //Signal thread to continue to next WaitOne(). //signal.Set(); Console.ReadLine(); } //static AutoResetEvent signal = new AutoResetEvent(false); //While using AutoResetEvent the Set() method has to be called once for every WaitOne() method used. static ManualResetEvent signal = new ManualResetEvent(false); //ManualResetEvent only reqiures the Set() method to be called once. public static void doWork(object threadNumber) { Console.WriteLine("Thread " + threadNumber.ToString() + " has started."); //The code will stop executing here and wait until it gets signaled to continue. signal.WaitOne(); //Simulate doing time consuming work. Thread.Sleep(1500); Console.WriteLine("doWork() is doing it's work."); //The code will stop executing here and wait until it gets signaled to continue. signal.WaitOne(); Console.WriteLine("Thread " + threadNumber.ToString() + " is done."); } } }
Resulting output:
2.1 AutoResetEvent
2.1 AutoResetEvent