I am again here to continue the discussion around Threading. Today we will discuss about Semaphore and related concepts. Let’s start by putting some questions to understand the concepts.
What is Semaphore in Multithreading?
Semaphore is yet another synchronization construct in multithreading which is used to limit the number of threads allowed to access any code or logic.
When Semaphore is required how is it different from lock and mutex?
Lock and mutex are used to control synchronization by allowing only one thread at a time to access any resource whereas Semaphore allows more than one thread to access resource or code block.
Also semaphore is thread-independent means any thread can release it unlike lock and mutex where only the owner thread or the thread which obtained them can release.
What is named and unnamed Semaphore?
If Semaphore is named, it can be used to control synchronization across processes in an operating system however unnamed semaphore can only be used across app domains inside a process.
What is SemaphoreSlim?
SemaphoreSlim is an alternative of Semaphore. It is introduced in .NET version 4.0. SemaphoreSlim is a lightweight or slim version of Semaphore and it is recommended to be used when waiting time is expected to be very less. Also SemaphoreSlim can only be used within a single process similar to unnamed Semaphore.
SemaphoreSlim also supports cancellation token that can be used when waiting.
How Semaphore works?
Movie theater analogy can be put to understand working of Semaphore. In Movie theaters there are fix number of seats people can occupy. Once movie is over, another set of audience occupies the same seats.
Semaphore works in the same way. When declaring semaphore, we define the capacity or the number of allowable threads that can enter into code block.
At runtime, we can add the threads into the semaphore till the allowable capacity. Other threads are kept in the queue and enters when existing threads are released.
How to use Semaphore in an application?
Let’s understand this by simple example.
Here we will create Semaphore with three capacity and initial count to start with. We will span five odd threads to execute DoWork method.
- public class TestSemaphore
- {
- private static Semaphore _sema = new Semaphore(3, 3);
- private static int _count = 0;
- public static void DoWork(object threadId)
- {
- Console.WriteLine("Thread {0} is in queue of Semaphore. Count: {1}", threadId, _count);
- _sema.WaitOne();
- _count++;
- Console.WriteLine("Thread {0} has entered in Semaphore. Count: {1}", threadId, _count);
- Thread.Sleep(1000);
- _count--;
- _sema.Release();
- Console.WriteLine("Thread {0} has exited from Semaphore. Count: {1}", threadId, _count);
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- Console.Title = "Semaphore Demo";
- for (int i = 1; i <= 5; i++)
- {
- new Thread(TestSemaphore.DoWork).Start(i);
- }
- }
- }
Output:
As you can see in the output although five threads are trying to execute DoWork method but one at a time, only maximum of three threads are allowed to enter into code enclosed with Semaphore, other threads are simply waiting or queued and able to enter only when at least one of entered/running thread has exited.
So at this point, we have seen how to synchronize threads using Semaphore. Now let’s see how to work with SemaphoreSlim.
As previously stated, SemaphoreSlim is just another lightweight API that works similarly to unnamed Semaphore. That’s true but in addition, SemaphoreSlim comes with some new properties.
Let’s modify the same example to work with SemaphoreSlim.
- public class TestSemaphoreSlim
- {
- private static SemaphoreSlim _semaSlim = new SemaphoreSlim(3);
- public static void DoWork(object threadId)
- {
- Console.WriteLine("Thread {0} is in queue of SemaphoreSlim. Count: {1}", threadId, _semaSlim.CurrentCount);
- _semaSlim.Wait();
- Console.WriteLine("Thread {0} has entered in SemaphoreSlim. Count: {1}", threadId, _semaSlim.CurrentCount);
- Thread.Sleep(1000);
- _semaSlim.Release();
- Console.WriteLine("Thread {0} has exited from SemaphoreSlim. Count: {1}", threadId, _semaSlim.CurrentCount);
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- Console.Title = "SemaphoreSlim Demo";
- for (int i = 1; i <= 5; i++)
- {
- new Thread(TestSemaphoreSlim.DoWork).Start(i);
- }
- }
- }
As you can see in the code above that, SemaphoreSlim constructor takes only one parameter for capacity. Also we no longer need the count variable, instead SemaphoreSlim out of the box provides CurrentCount property.
Let’s see the output now.
Output
As you can see although APIs are a little different in Semaphore and SemaphoreSlim, output is similar where number of allowed threads are controlled and that is what Semaphore is all about.
Hope you have liked the article. Look forward for your comments/suggestions.