C# Multithreaded Resource Access – Synchronization

C# provides us many ways to safely handle shared resources from than one threads/tasks. In this blog post we will discuss the different types that exist (and they are many), the different programming patterns they are used for, their benefits and their drawbacks.

In this article we will start with the basics.

 

 

 

Locking

So what is locking?
A locking state (or exclusive locking) is basically ensuring that only one thread can enter particular sections of code at a time.
For example if two threads want to manipulate a shared resource at any given moment, then we can introduce locking around the variable manipulation code to ensure that only one thread at a time will perform any code on it.
Two main types of exclusive locking exist in C#:

  • lock
  • Mutex

Out of the two, the most efficient one is lock.
Let’s consider the following class.

The above code is not thread safe. If the account is shared, and one person is withdrawing from that account while another person is depositing, we may get wrong results as a result. For example if the deposit method produces the new m_totalAmount value, then the thread is removed from the CPU and the Withrdawing thread is in. The withdraw thread removes an amount from the total amount of the bank account. Then the deposit thread is back in the CPU, and stores the value it initially calculated .
So you understand we need a way to safeguard against this. Well here comes the lock mechanism.

So with the above code we ensure that at any given moment only one of the two methods will enter the critical area where the shared variable is accessed.
What the lock does though? The lock is basically telling everyone that code will now enter the critical area and is requesting access. In case any other code is in the same critical area (area using the same lock object) then the code will block and wait until the lock is released by the other thread that is currently holding the lock.

At the start of the article we mentioned two types of blocking mechanisms. One mechanism we did not mention is Monitor.
Achieves exactly the same but has also extra capabilities. In fact the lock statement is exactly equivalent to

It consumes the same time and resources and achieves the exact same result.
Monitor is a static class that can never be instantiated. The difference between Monitor and Lock is that using Monitor provides with more advanced usage.
For example, at any point within an Enter-Exit block, a thread can call Monitor.Pulse(object) which notifies all threads that are waiting to execute that they can enter the critical area. and then use Monitor.Wait(object) to wait for them to finish their code.
This though is not the same as the Enter-Exit block and, as I mentioned, are for very advance usage and I did not need to use in my programming career so far.

A thread can repeatedly lock (or Monitor.Enter) the same object in a nested fashion
e.g.

Now this case is very useful in cases where we need to call another method that also accesses the same shared resource and performs a lock on the object that we already received.

For example if we had another method in our bank account that was simply just requesting the remainder of what we have. We would like to lock on that resource so at the moment of access, no other user would access that amount (yes the example seems trivial and useless but let’s see how it goes).

All the code still keeps working. Although Deposit and Withdraw methods are calling another method that locks on the same object that is currently locked, due to it being a nested lock call, access is given directly.

Just a reminder though, the object is only fully “unlocked” when all nested locks have “unlocked”.

One thing to avoid is having nested locks on a different object as if not handled properly are very dangerous and can leade to Deadlocks.
A deadlock happens when two (or more) threads, each wait for a resource held by the other thus not allowing anyone to proceed.
The easiest way to illustrate is with a trivial example

So in the above code, both threads first lock on their own lock, then try to lock on the other threads lock and stay there, waiting for it to be released.
A good rule of thumb is to avoid nested locks on different objects and try to re-write your to remove the need for nested locks.

Now let us move on to Mutex. Mutex stands for Mutual Exclusion.
With Mutex class you call the Mutex.WaitOne method to lock and Mutex.ReleaseMutex method to unlock.
The first difference in usage compared to Monitors and locks is that you need to create a Mutex object and calling methods from the object itself instead of using the object as an argument in the lock and Monitor methods.
The second and more meaningful difference between Mutex and locks is that Mutex are around 50 times slower than locks.
The third and most meaningful difference between Mutex and locks (and the reason Mutex exists) is that Mutex can work across threads like locks, but can also work across processes. So a mutex can actually be computer-wide which is the main difference. Other than that Mutex is exactly the same as a lock.

A common use case for Mutex is to allow only instance of software to run simultaneously.

So this is it for our first part of this series.
In future articles we will talk about semaphores, resetEvents and non-blocking synchronization.

Leave a Reply