Synchronization by lock and Monitor statements in C#

Kevin Salimi
5 min readFeb 28, 2021

--

Working through enterprise applications has various challenges, among them, multi-threading stands out. Nowadays, all applications tend to use all capabilities of modern hardware, especially CPUs in our case. Performing several jobs simultaneously by multiple threads brings about more advantages for us like high processing speed, however, without having enough knowledge on this matter, our application outcomes may go toward deterioration.

Fighting for grabbing resources not only belongs to real-life environments but also is the truth on the wire. In such scenarios, we have a situation that must be addressed, otherwise, something undoubtedly happens which commonly called a race condition. It means an undesirable situation that occurs when a device or system attempts to perform two or more operations in a time.

Our duty as a programmer is to establish certain policies in order to bring fairness and balance among resources and consumers. By doing this, the mess would be wrapped up.

Synchronization

Whenever two or more threads tend to reach out to a shared-resource at a time, the system needs a synchronization mechanism to ensure that only one thread has exclusive access to the resource. Synchronizing access to data is a safe way to prevent race conditions from happening along with the system.
There are plenty of mechanisms to synchronize an application in .NET. This blog post goes over `lock` and `Monitor` statements.

lock statement

The `lock` is widely accepted due to its simplicity. However, that advantage has a downside: programmers use it without really considering what it does and how to use it efficiently. Knowing it and best practices bring you proper insight when coding in concurrent scenarios.

It ensures that only one thread can enter a critical section of codes. Other threads that try to be the owner of the `lock` would be suspended until the first thread releases the `lock`.

As you can see, the `lock` guarantees that only one thread could increment the value of the `counter`. The `lockerObject` is a synchronizing object that can be locked by only one thread at a time.

Choosing the right synchronization object

Although any object can be used as a synchronization object, there is one hard rule, “it must be a reference type”. The synchronization object is typically a private and a static or an instance field. A private member helps to encapsulate the locking object. As a result, it can prevent deadlock, since unrelated code could choose the same object to lock on for different purposes.

Also, you can use `this` object as a synchronizing object. For example:

As mentioned, locking in this way has a serious issue that is not encapsulating the locking logic and can prone to deadlock.

Briefly, the most reliable way for choosing a synchronizing object is to consider a private object in the class by which you can have precise control over it.

Under the hood

The `lock` statement is translated to the following code in C# 3.0:

There is a vulnerability in this code. If an exception is being thrown between `Monitor.Enter()` and `try` due to for example `OutOfMemoryException` or the thread is being aborted, also if `lock` is being taken, it is never released because the thread never gets into the `try` block and causes leaked lock.

This danger has been fixed in C# 4.0. the following overload is added to `Monitor.Enter`.

Eventually, from C# 4.0 onward the `lock` statement is translated to the following code. As you see, the vulnerability that we saw before has healed.

In short, the `lock` is shorthand for utilizing the `Monitor` statement. Let’s take a closer look at it.

The `Monitor` statement

The `Monitor` is another way of synchronization in .NET with which a lock object has exclusive access to a particular block of code. It is a static class that provides several methods to operate on an object that controls access to the critical section. An object can grab the ownership of the Monitor by calling `Monitor.Enter`, and release it by `Monitor.Exit`. While a thread owns the lock for an object, no other thread can acquire that lock.

The `Monitor` never be released unless the owner of `Monitor` must pass to `Exit`. Therefore, if you pass an object to `Exit` which is not the owner of the Monitor, you will get the `SynchronizationLockException` exception with the message “Object synchronization method was called from an unsynchronized block of code.

Lock on a Reference Type, Not a Value Type

Despite the `lock` that only accepts reference types as a lock object, `Enter` accepts reference types and value types. Bear in mind that using value types as a lock object leads to automatic “boxing” (i.e. wrapping), then a reference type would be the ownership. Automatic boxing creates a new object for each invocation of `Enter`, so `Exit` attempts to find the lock for the box object but finds nothing and throws the mentioned exception. That leads to a leaked lock since the owner object never releases the `Monitor`.

The below codes show the point:

If we box the `locker` then passed through the `Enter` the above exception never happens. For example:

Monitor Sample

So far, we have learned concepts and fundamental principles of the `Monitor`. It is time to practice, so let’s dive through a short sample.

The below code lunches ten tasks, each of which sleeps for 200 milliseconds and increments the value of the `counter` variable. The critical section enclosed by `Enter` and `Exit` methods so that Our program works well and displays the expected result on the screen.

Monitor static methods

The below table contains static methods of the `Monitor` class with a short description.

Conclusion

The `lock` statement is an easy way to bring thread safety to your code and allowing only one thread to synchronize access to a particular block of code. We see that the `lock` stems from `Enter/Exit` methods of the `Monitor` class. Also discussed best practices that help us to gain a deeper insight for moving toward thread safety.

Here is not the end of our story; in the next post, we will go through some practical samples and see how `lock` is used in conjunction with other `Monitor` methods for a more advanced form of thread coordination.

Next mission

The post paves the way for the next mission in which we will dive into details of `Wait`, `Pulse`, and `PulseAll` then will implement a simple producer-consumer problem by the capabilities of Monitor static class.

--

--