In the previous article, we talked about “Introduction to Monitor Class In C#”. So, in this article, we will take a detailed look at the Monitor class.
The Monitor class is a hybrid thread synchronization construct. It provides a mutual exclusive lock with supporting spinning, thread ownership, and recursion.
Hybrid Synchronization Constructs
Hybrid Synchronization Constructs are the combining the user-mode synchronization constructs and kernel-mode thread synchronization constructs.
The user-mode synchronization constructs are application level and faster than the kernel-mode synchronization constructs. Because it uses special hardware-level instructions; which it makes faster.
The user-mode synchronization constructs are:
Using user-mode constructs has some disadvantages:
- The thread wants to execute some code block, but another thread is currently running, then it is put on hold, but this hold is simulated by spins on the CPU, which wastes CPU time.
- The threads are blocked through spins. For that reason, the Windows operating system never detects in case of it. So, in the case of a livelock (the thread is running on a CPU forever) situation will waste both CPU time and memory (the worst one).
The kernel-mode synchronization constructs are provided by the Windows operating system. The threads call functionalities are implemented at the level of the operating system kernel. But the advantage of this mode is when a thread wants to acquire a resource that another thread has, Windows blocks the thread, and when a resource becomes available, resumes the thread again, so the Windows throughs that achieve not wasting the CPU time.
Also, in case a thread holding the construct and another one is waiting for the construct, it might be blocked forever. Well, it is called deadlock, which wastes only memory (better than livelock because livelock wastes both CPU and memory).
The kernel-mode synchronization constructs are:
- AutoResetEvent
- ManualResetEvent
- Semaphore
- Mutex
As a result of these, preferable to use fast and non-blocking constructs. This is where Hybrid Synchronization Constructs come in!
User-mode synchronization constructs are fast and non-blocking. It is very effective, but in the case of racing/competition (needs blocking + efficiency) would be better to use Kernel-mode synchronization constructs. Also, note that the Kernel mode can block (stop) the threads, but the User-mode can only spin in case of the blocking (for that reason, they are non-blocking, on the other hand they help us to reuse threads).
Hybrid synchronization constructs combine the user-mode and kernel-mode constructs and provide more effective constructs.
How Monitor Class Works Internally?
As we see from the image above, the image contains the Managed heap and CLR’s Array of Sync Blocks. Well, when CLR allocates the object instance in a heap, it has some fields. One of them is type object pointer and another one is sync block index. The sync block index belongs to thread synchronization, and it contains an integer index in the array of sync blocks. So, CLR also, for efficiency, creates the array of sync blocks (in a native heap) that provides association with type through the index number.
Let’s think that we constructed an object (initialized). So, when the Object is constructed its sync block index assigns -1 by default. Which means that there is no reference to any sync block. Well, let’s assume that we want to use that object as an input parameter in Monitor.Enter method. So, in this case, CLR will find the free index in CLR’s array of sync blocks, occupy it, and then set the index into the object sync block (it would be on the fly reference).
In the case of the Monitor.Exit method, the used object sync block index will assign -1. And CLR’s array of sync blocks will be free. As we see the sync block index looks like a conditional flag (indicates the locked or not locked).
There are Some Problems With the Monitor Class
When using a monitor, there are some significant problems. So, be careful when using the Monitor class.
- If Monitor.Enter input parameter is derived from the System.MarshalByRefObject class, it means there is a proxy object between the client (who working with Monitor class) and actual object. So, locking will be applied to the proxy one.
- Assume that we have 2 strings with the same value (as you know strings can be interned) and belong to separate pieces of code. So, when we pass a one of string as an input parameter to Monitor.Enter method both strings will synchronize their execution with each other unknowingly (the same situation will be a problem for the “this” keyword). In the case of both using the thread synchronization construct, a deadlock may occur.
- If we pass a value type as an input parameter to Monitor.Enter method, causes the value type to get boxed, resulting in the thread taking a lock on the boxed object. Each time Monitor.Enter is called, a lock is taken on a completely different object and you get no thread synchronization at all.
If you want to learn more, you can check out the book "CLR via C# by Jeffrey Richter".
Stay tuned!