c sharp

How to Implement and Use Semaphores in C#

Multi-threading is a critical factor in high-performance applications. One of the most crucial part of multi-threading is semaphores. These are essential features that allow us to manage the number of threads that can access a given resource simultaneously.

In this tutorial, we will cover the basics of implementing and working with semaphores using the Semaphore and SemaphoreSlim classes from the System.Threading namespace.

Semaphore and SemaphoreSlim Classes

There two main classes of implementing semaphores in C#. The first is the “Semaphore” class.

C# Semaphore Class

This class represents a named semaphore. The named semaphore can be a local or systemwide entity. It works as a thin wrapper around the Win32 semaphore object which are used to control the access to a pool of resources.

C# SemaphoreSlim Class

The second class is the “SemaphoreSlim” class. This provides a lightweight and faster semaphore that is used to handle a single process with very short wait times. Unlike the “Semaphore” class, the “SemaphoreSlim” class does not support named semaphores.

It does, however, have native support for cancellation tokes and wait handle for synchronization.

Implementing a C# Semaphore Using the Semaphore Class

The “Semaphore” class helps to limit the number of threads that can access a resource or a pool of resources at the same time.

The syntax is as follows:

public sealed class Semaphore : System.Threading.WaitHandle

Example Usage:

The following example shows the basics of creating a semaphore using the “System.Threading.Sempahore” class with a maxim count of 3 and an initial count of 0:

using System;
using System.Threading;

class Program
{
    // 3 count, initial 0
    private static Semaphore _pool = new Semaphore(0, 3);

    static void Main(string[] args)
    {
        // 5 threads,  3 at once
        for (int i = 1; i <= 5; i++)
        {
            Thread t = new Thread(new ParameterizedThreadStart(Slot));
            t.Start(i);
        }

        // release 3 threads on enter key.
        while (true)
        {
            Console.ReadLine();
            _pool.Release(3);
        }
    }

    static void Slot(object id)
    {
        Console.WriteLine(id + " is waiting for the slot.");
        _pool.WaitOne();

        Console.WriteLine(id + " has entered the slot.");
        Thread.Sleep(1000 * (int)id);
        Console.WriteLine(id + " is leaving the slot.");

        _pool.Release();
    }
}

In this example, we start by creating a named semaphore called “_pool” with an initial count of 0 and a maximum count of 3. This denotes that the semaphore can only allow up to three threads.

We then start five threads and wait for the semaphore to be released since the initial count is 0. Upon pressing the return key, the semaphore can allow the three threads to access the critical section.

Output:

1 is waiting for the slot.
2 is waiting for the slot.
3 is waiting for the slot.
4 is waiting for the slot.
5 is waiting for the slot.
<>Return Key
1 has entered the slot.
3 has entered the slot.
2 has entered the slot.
1 is leaving the slot.
4 has entered the slot.
2 is leaving the slot.
5 has entered the slot.
3 is leaving the slot.
4 is leaving the slot.
5 is leaving the slot.

Semaphore Implementation with SemaphoreSlim

Let us look at an implementation using the “SemaphoreSlim” class instead of the “Semaphore” class.

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    private static SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(2);

    static void Main()
    {
        Task[] tasks = new Task[6];

        for (int i = 0; i < 6; i++)
        {
            tasks[i] = Task.Run(() => AccessDatabase(i));
        }

        Task.WaitAll(tasks);
    }

    static async Task AccessDatabase(int id)
    {
        Console.WriteLine($"Task {id} is requesting access.");
        await _semaphoreSlim.WaitAsync();
        Console.WriteLine($"Task {id} accessing resource");

        //  resource usage
        await Task.Delay(1000);

        Console.WriteLine($"Task {id} access terminated..");
        _semaphoreSlim.Release();
    }
}

In this case, we use the “SemaphoreSlim” to simulate a single process that has multiple threads. We start by initializing the “SemaphoreSlim” with a count of 2. Hence, only two threads can access the resource concurrently.

We then create six tasks and try to access the resource. Since only two are allowed, the remaining tasks have to wait for a space to be available.

Conclusion

In this tutorial, we used the “Semaphore” and “SemaphoreSlim” classes in C# to implement and use the basic semaphores. Feel free to reference the documentation for each of these classes to discover more.

About the author

John Otieno

My name is John and am a fellow geek like you. I am passionate about all things computers from Hardware, Operating systems to Programming. My dream is to share my knowledge with the world and help out fellow geeks. Follow my content by subscribing to LinuxHint mailing list