Making the most out of semaphores

Haritha Mohan,design

When utilizing a shared resource in an application, it can be of great importance to ensure that threads get exclusive access to the resource to prevent detrimental issues like deadlocks, data corruption, and resource contention.

Deadlocks are the culprit

Deadlocks occur when multiple threads occur when multiple threads are trying to access the same resource but in this overambitious effort all the threads in question end up not being able to access the resources they need because they are all waiting for the other to release their current resource that they are latched onto, leading your threads to look something like this:

Photo

“Deadlock occurs when a resource is locked by a thread but also required by another at the same time” (TutorialsPoint (opens in a new tab)).

So when exclusive access is necessary, a lock statement can be used to control and limit access as needed. The official Microsoft documentation (opens in a new tab) does a great job explaining the fix. By employing the use of a lock object (note how the sole purpose of this object is to serve as a lock for the resource and nothing more), it serves as a protective barrier to the resource and oversees that the threads are fighting each other for control -> tldr: use a lock to prevent deadlocks.

Example

Let’s take a look at an example of using a lock to prevent deadlocks when opening a database connection.

Photo

Ok cool, locks are good! The code above is great for synchronous behavior, but what if we throw async behavior into the mix? Perhaps, something like this?

Photo

This is invalid C#!!!!

You will see an error like this:

An await expression cannot be used in a synchronous function, in a query expression, in the catch or finally block of an exception handling statement, in the block of a lock statement, or in an unsafe context.

But why? By having await behavior nestled inside a lock object, we have returned to our og issue -> a deadlock. But now rather than the database connection being sought after by the threads, it is actually the lock itself that’s the deadlock victim now.

If there is a will, there is always a way…and this case is no exception. And the way is SemaphoreSlim (opens in a new tab).

First of all, it's a pretty bad ass name for an API. The fix it provides is almost as cool. Semaphores are typically used as a signaling mechanism to control access between shared resources. You can specify the initial and maximum number of threads allowed to access said resource. And comparing semaphores against it’s sister mechanism, Mutex, is actually quite interesting but more on that later..

Anyways, in our case we want to limit our database connection access to a single thread at a given time. So, we can use SemaphoreSlim (“a lightweight alternative to Semaphore”) like this:

Photo

We are using the semaphore, which can handle the async behavior, as a “lock”. And the potential for deadlocking is no longer an issue because the semaphore, unlike the original lock object, can be awaited. Through using the try, finally block we can ensure that the semaphore is released after we are doing using it so it can be reused as a valid lock mechanism when dealing with the database connection later (for ex, closing the connection).

So deadlocking crisis averted and your threads will look more like this now:

Photo © Haritha Mohan.RSS