What Does a Lock Statement Do Under the Hood

What does a lock statement do under the hood?

The lock statement is translated by C# 3.0 to the following:

var temp = obj;

Monitor.Enter(temp);

try
{
// body
}
finally
{
Monitor.Exit(temp);
}

In C# 4.0 this has changed and it is now generated as follows:

bool lockWasTaken = false;
var temp = obj;
try
{
Monitor.Enter(temp, ref lockWasTaken);
// body
}
finally
{
if (lockWasTaken)
{
Monitor.Exit(temp);
}
}

You can find more info about what Monitor.Enter does here. To quote MSDN:

Use Enter to acquire the Monitor on
the object passed as the parameter. If
another thread has executed an Enter
on the object but has not yet executed
the corresponding Exit, the current
thread will block until the other
thread releases the object. It is
legal for the same thread to invoke
Enter more than once without it
blocking; however, an equal number of
Exit calls must be invoked before
other threads waiting on the object
will unblock.

The Monitor.Enter method will wait infinitely; it will not time out.

c# why put object in the lock statement

I've made a very simple class to illustrate what the object in the lock is there for.

public class Account
{
private decimal _balance = 0m;
private object _transactionLock = new object();
private object _saveLock = new object();

public void Deposit(decimal amount)
{
lock (_transactionLock)
{
_balance += amount;
}
}

public void Withdraw(decimal amount)
{
lock (_transactionLock)
{
_balance -= amount;
}
}

public void Save()
{
lock (_saveLock)
{
File.WriteAllText(@"C:\Balance.txt", _balance.ToString());
}
}
}

You'll notice that I have three locks, but only two variables.

The lines lock (_transactionLock) mutually lock the regions of code to only allow the current thread to enter - and this could mean that the current thread can re-enter the locked region. Other threads are blocked no matter which of the lock (_transactionLock) they hit if a thread already has the lock.

The second lock, lock (_saveLock), is there to show you that the object in the lock statement is there to identify the lock. So, if a thread were in one of the lock (_transactionLock) statements then there is nothing stopping a thread to enter the lock (_saveLock) block (unless another thread were already there).

Purpose of object in argument of Lock statement

The object passed to the lock statement can be thought of as an identifier for the lock. For any object, only a single thread can obtain an exclusive lock on that object at any given time. By allowing an object to be passed to the lock statement, you can use different objects for different areas of critical code.

The Monitor.Enter documentation provides a better description of the use of the object than does the lock statement documentation.

Threading: Locking Under the hood of

What happens under the hood is approximately this:

  • Imagine the object type has a hidden field in it.
  • Monitor.Enter() and Monitor.Exit() use that field to communicate with each other.
  • Every reference type inherits that field from object.

Of course, the type of that field is something special: It’s a synchronization lock that works in a thread-safe manner. In reality, of course, it is not really a field in the CLR sense, but a special feature of the CLR that uses a chunk of memory within each object’s memory to implement that synchronization lock. (The exact implementation is described in “Safe Thread Synchronization” in the MSDN Magazine.)

How come it still provides thread safety? I think what you mean is: why doesn’t it break thread safety for objects that are thread safe? The answer is easy: because you can have objects that are partly thread safe and partly not. You could have an object with two methods, and using one of them is thread safe while the other isn’t. Monitor.Enter() is thread safe irrespective of what the rest of the object does.

Why only reference types to be used for locking? Because only reference types actually have this special magic in their memory chunk. Value types are really literally just the value itself: a 32-bit integer in the case of int; a concatenation of all the fields in the case of a custom struct. You can pass a value type into Monitor.Enter(), and it won’t complain, but it won’t work because the value type will be boxed — i.e., wrapped into an object of a reference type. When you call Monitor.Exit(), it will be boxed again, so it will try to release the lock on a different object reference.

Regarding your code sample: I see nothing wrong with it. All your access to the stringList variable is wrapped in a lock, and you never assign to the stringList field itself except during initialisation. There is nothing that can go wrong with this; it is thread safe. (Of course something could go wrong if some other code accesses the field without locking it. If you were to make the field public, there is a very great chance of that happening accidentally. There is no need to use only locally-scoped variables for such a lock unless you really can’t ensure otherwise that the variable won’t be accessed by code you don’t control.)

Will lock() statement block all threads in the process/appdomain?

You have a hierarchy of objects:

  • You have servers (10)
  • On each server you have processes (probably only 1 - your service/app pool)
  • In each process you have threads (probably many)

Your code will only prohibit threads within the same process on the same server access to modify the Cache object simultaneously. You can create locks across processes and even across servers, but the cost increases a lot as you move up the hierarchy.

Using the lock statement does not actually lock any threads. However, if one thread is executing code inside the lock (that is in the block of code following the lock statement) any other thread that wants to take the lock and execute the same code has to wait until the first thread holding the lock leaves the block of code and releases the lock.

The C# lock statement uses a Windows critical section which a lightweight locking mechanism. If you want to lock across processes you can use a mutex instead. To lock across servers you can use a database or a shared file.

As dkackman has pointed out .NET has the concept of an AppDomain that is a kind of lightweight process. You can have multiple AppDomains per process. The C# lock statement only locks a resource within a single AppDomain, and a proper description of the hierarchy would include the AppDomain below the process and above the threads. However, quite often you only have a single AppDomain in a process making the distinction somewhat irrelevant.

Why do we use private object with lock statement?

Why we need private obj?

It doesn't actually have to be private, the code would work just as well with a public object. And you can lock on any object.

But it is a best practice to hide the lock object as much as possible, and only make it accessible to the code that actually needs it. This helps to prevent deadlocks.

C# Lock Statement

Monitor (which is used by the lock statement under the covers) is reentrant, so it's technically ok for the same thread to lock on an object multiple times. The lock will be released when the outer lock scope completes. However, reentrant locks are difficult to reason about and should be avoided unless you have no other option.

Deadlocks do not occur due to reentrant locks. They occur when you take out locks on multiple objects while some other thread locks the same objects in a different order.

Confusion about the lock statement in C#

The question is confusingly worded and the answers so far are not particularly clear either. Let me rephrase the question into several questions:

(1) Does the lock statement ensure that no more than one thread is in the body of the lock statement at any one time?

No. For example:

static readonly object lock1 = new object();
static readonly object lock2 = new object();
static int counter = 0;
static object M()
{
int c = Interlocked.Increment(ref counter);
return c % 2 == 0 ? lock1 : lock2;
}

...
lock(M()) { Critical(); }

It is possible for two threads to both be in the body of the lock statement at the same time, because the lock statement locks on two different objects. Thread Alpha can call M() and get lock1, and then thread Beta can call M() and get lock2.

(2) Assuming that my lock statement always locks on the same object, does a lock statement ensure that no more than one "active" thread is in the body of the lock at any one time?

Yes. If you have:

static readonly object lock1 = new object();
...
lock(lock1) { Critical(); }

then thread Alpha can take the lock, and thread Beta will block until the lock is available before entering the lock body.

(3) Assuming that I have two lock statements, and both lock statements lock on the same object every time, does a lock statement ensure that no more than one "active" thread is in the body of either lock at any one time?

Yes. If you have:

static readonly object lock1 = new object();
...
static void X()
{
lock(lock1) { CriticalX(); }
}
static void Y()
{
lock(lock1) { CriticalY(); }
}

then if thread Alpha is in X and takes the lock, and thread Beta is in Y, then thread Beta will block until the lock is available before entering the lock body.

(4) Why are you putting "active" in "scare quotes"?

To call attention to the fact that it is possible for a waiting thread to be in the lock body. You can use the Monitor.Wait method to "pause" a thread that is in a lock body, and allow a blocked thread to become active and enter that lock body (or a different lock body that locks the same object). The waiting thread will stay in its "waiting" state until pulsed. At some time after it is pulsed, it rejoins the "ready" queue and blocks until there is no "active" thread in the lock. It then resumes at the point where it left off.

C# lock keyword: Why is an object necessary for the syntax?

Because you don't just lock - you lock something (you lock a lock).

The point of locking is to disallow two threads from directly competing for the same resource. Therefore, you hide that resource behind an arbitrary object. That arbitrary object acts as a lock. When one thread enters a critical section, it locks the lock and the others can't get in. When the thread finishes its work in the critical section, it unlocks and leaves the keys out for whichever thread happens to come next.

If a program has one resource that's candidate for competing accesses, it's possible that it will have other such resources as well! But often these resources are independent from each other - in other words, it may make sense for one thread to be able to lock one particular resource and another thread in the meantime to be able to lock another resource, without those two interfering.

A resource may also need to be accessed from two critical sections. Those two will need to have the same lock. If each had their own, they wouldn't be effective in keeping the resource uncontested.

Obviously, then, we don't just lock - we lock each particular resource's own lock. But the compiler can't autogenerate that arbitrary lock object silently, because it doesn't know which resources should be locked using the same lock and which ones should have their own lock. That's why you have to explicitly state which lock protects which block (or blocks) of code.

Note that the correct usage of an object as a lock requires that the object is persistent (at least as persistent as the corresponding resource). In other words, you can't create it in a local scope, store it in a local variable and throw it away when the scope exits, because this means you're not actually locking anything. If you have one persistent object acting as a lock for a given resource, only one thread can enter that section. If you create a new lock object every time someone attempts to get in, then anyone can enter at all times.



Related Topics



Leave a reply



Submit