Threading deep dive – Day 6

What are locks?

When there is a shared value that can be accessed by all the threads from your application, data become invalid is possible. Remember we discussed this issue in previous days. So we have to synchronize the access across all threads. How? There come the locks. There is "lock" statement available in C# using which one can lock on some reference type and proceed using the resource. If all other threads from your application also follow this approach [locking on one specific reference type and then continue using the resource], CLR guarantees that would allow only one thread at a time in the lock controlled statements. All other threads would be waiting on the lock statement itself till the thread that gone inside the lock block completes its execution and come out of the block. You may aware that lock internally uses Monitor based locks. There are different kinds of locking mechanisms available with CLR like Monitors, Mutexes, Semaphores etc. You can use these locks based on your requirements like Inter-appdomain, Inter-process , intra-appdomain and intra-process locking.

Below code sample describes the lock usage in C# 

object lockOb = new object();

lock (lockOb)

{

// some operations

}

 

Below is the call stack of the thread that waits on the lock

ntdll!KiFastSystemCallRet

ntdll!NtWaitForMultipleObjects

KERNEL32!WaitForMultipleObjectsEx

mscorwks!WaitForMultipleObjectsEx_SO_TOLERANT

mscorwks!Thread::DoAppropriateAptStateWait

mscorwks!Thread::DoAppropriateWaitWorker

mscorwks!Thread::DoAppropriateWait

mscorwks!CLREvent::WaitEx

mscorwks!CLREvent::Wait

mscorwks!AwareLock::EnterEpilog

mscorwks!AwareLock::Enter

JIT_MonEnterWorker_Portable

   

I have taken this stack using winDBG. The call stack reveals that the thread could not acquire the lock, waits by calling the function NtWaitForMultipleObjects from ntdll.dll and then transitions to kernel mode by calling KiFastSystemCallRet function from the same dll. Here you may get the question in your mind like why the thread should transition into kernel mode for waiting? We will look later the relationship between thread waiting and kernel mode transitioning.

   

We use lock statements to synchronize access to resources. Is there any other ways available to synchronize data access? Yes.

  1. We can make use of memory barriers which would force the processor to write all pending writes to RAM.

  2. Lock free data structures where you need not to lock for read / write operations.

  3. Interlocked operations that perform operations atomically.

       

    Tomorrow let us see, what are atomic operations and their internals.