1

I saw jeffery richter video ( click to see exact line) and he says :

It is always better to use Monitor.Enter and Monitor.Lock over Event-wait-handles or Semaphore etc because they (monitor.X) use kernel object , but they only use them if there's contention. and if there's no contention , they dont use those objects.

I maybe missing something here but when I do :

lock(myObj)
{
 ...
}

I presume that there might be more than one thread who wants to get into the critical section.
So , according to the info above , if there no contention , the lock wont be used ? (what if another thread is about to enter 1 ms after?)

Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • Why have you emphasised those words in the quote? Does he emphasis them? – Ash Burlaczenko Mar 16 '13 at 09:06
  • @AshBurlaczenko I believe it will help others to see what are the important words (for me) which my question is about. (are you that disturbed with that ? you are welcome to remove them) – Royi Namir Mar 16 '13 at 09:10
  • It's just that if your quote someone imo you should put it as they say it. If you think it helps thats fine. – Ash Burlaczenko Mar 16 '13 at 09:12

3 Answers3

3

Yes, the Monitor class is very nicely optimized in the CLR. It is very cheap if no other thread already owns the monitor. You don't even pay for extra storage, the locking state is stored in a field in Object that every object already has available, part of the object header.

The Monitor.Enter() method avoids entering OS code by first checking if the lock is already owned by the same thread. Which makes it re-entrant, it simply increments the lock count if that's the case. It next tries to acquire the lock by using the equivalent of Interlocked.CompareExchange(), a very cheap primitive on any processor. The x86 version of it is notable for not actually taking a bus lock at all, you can see the code for it in this answer.

If that didn't work then the operating system needs to get involved because now it is important that it can make a thread context switch to wake the thread back up when the lock is released. Windows highly favors picking a thread that was waiting for an OS synchronization object, which ensures it starts running again as quickly as possible. The underlying object is a simple event, a very cheap OS object. It also takes care of fairness, waiting threads are put in a queue and released in first-in, first-out order. I documented the underlying CLR code in this answer.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
2

The lock statement is just syntactic sugar which uses Monitor.Enter and Monitor.Exit as its implementation.

The Monitor functions themselves are implemented using Condition Variables. Their implementation means that they don't need to allocate a kernel object unless there actually is contention for the lock. When that happens, they do have to go ahead and allocate a kernel object.

Even if there is lock contention, they don't immediately allocate a kernel object. Instead, they "spin" (just sit in a tight loop for a short while) in the hope that the lock becomes free. Only if it doesn't become free will it go on to allocate/use a kernel object.

Note that some of the new synchronisation classes, such as ManualResetEventSlim take this approach too. (In general, any synch class with "Slim" at the end does this.)

Also see this thread.

To directly answer your question about lock: Yes, if there's no contention, or if the contention only lasts a very short while, there will be no transition to kernel mode in order to use a kernel object. Only if the contention lasts more than a short while will a transition to kernel mode occur.

Community
  • 1
  • 1
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
2

So , according to the info above , if there no contention , the lock wont be used ? (what if another thread is about to enter 1 ms after?)

Correct. Then there's contention, and the other thread will have to enter the kernel. Also, the thread that has the lock will also have to enter the kernel when it unlocks it.

The operations kind of look like this:

Lock:

  1. Try to atomically set the user-space lock variable from unlock to lock. If we do, stop, we're done.

  2. Increment the user-space contention count.

  3. Set the kernel-space lock to locked.

  4. Try to atomically set the user-space lock variable from unlock to lock. If we do, decrement the user-space contention count and stop, we're done.

  5. Block in the kernel on the kernel lock.

  6. Go to step 3.

Unlock:

  1. Atomically set the user-space lock variable from lock to unlock.

  2. If the user-space contention count is zero, stop, we're done.

  3. Set the kernel lock to unlocked.

Notice how if there's no contention, the lock operation only involves step 1 and the unlock operation only involves steps 1 and 2, all of which take place in user space.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • please clarify : at first , at time T there is no contention ! becuase there is only one winner !. the thread who first got the lock. so if im the winner, how can i know if there another thread to come later? I mean , for me as a winner , I will always see - no contention ! it is only the second thread who will see that the resource is already taken..... can you please explain ? – Royi Namir Mar 16 '13 at 11:44
  • @RoyiNamir: The winner only cares if another thread comes later when they go to unlock. In my example implementation, the losing thread tells the winner by incrementing the contention count which is checked in the unlock operation. (See step 2 of lock and step 2 of unlock.) A winning (no contention) lock is just step 1. A losing (contention) lock is 1, 2, 3, 4, 5, 6, 3, 4. A winning (no contention) unlock is just step 1 and 2. A losing (contention) unlock is 1, 2, 3. None of the "winning" paths have any kernel operations. – David Schwartz Mar 16 '13 at 11:45
  • Thanks. So what jeffery is actually say is : if thread (line #1) is trying to set the lock var from _unlock_ to _lock_ and succeeds , so actually DONT LOCK , i.e. : dont use kernel object. but if it fails to do that so go into lock mode ...correct ? – Royi Namir Mar 16 '13 at 11:52
  • @RoyiNamir: Yes, if it fails to get the lock in user-space, then it will have to arrange to have the kernel block it and arrange to tell the thread that holds the lock to unblock it in the kernel when it unlocks. (And it needs a few extra checks to cover possible race conditions should the unlock race with the contended lock.) – David Schwartz Mar 16 '13 at 11:55