3
votes

I'm trying to wrap my mind around what exactly happens in the lock statement.

If I understood correctly, the lock statement is syntactic sugar and the following...

Object _lock = new Object();

lock (_lock)
{
    // Critical code section
}

...gets translated into something roughly like:

Object _lock = new Object();

Monitor.Enter(_lock);
try
{
    // Critical code section
}
finally { Monitor.Exit (_lock); }

I have used the lock statement a few times, and always created a private field _lock, as a dedicated synchronization object. I do understand why you should not lock on public variables or types.

But why does the compiler not create that instance field as well? I feel there might in fact be situations where the developer wants to specify what to lock on, but from my experience, in most cases that is of absolutely no interest, you just want that lock! So why is there no parameterless overload of lock?

lock()
{
   // First critical code section
}

lock()
{
   // Second critical code section
}

would be translated into (or similar):

[DebuggerHidden]
private readonly object _lock1 = new object()

[DebuggerHidden]
private readonly object _lock2 = new object()

Monitor.Enter(_lock1);
try
{
    // First critical code section
}
finally { Monitor.Exit(_lock1); }

Monitor.Enter(_lock2);
try
{
    // Second critical code section
}
finally { Monitor.Exit(_lock2); }

EDIT: I have obviously been unclear concerning multiple lock statements. Updated the question to contain two lock statements.

4
Your suggestion seems like an equivalent to lock(this) which is not a very good idea.empi
@empi: no, it is not equivalent to lock(this) since _ lock is a membermousio
@mousio: it will be equivalent functionally since there will be one lock object (_lock) for class instanceempi
@empi: you just explained why it is not equivalent; what about static methods requiring a lock?mousio
@adabyron you already have an attribute that can be applied to a method for this [MethodImpl(MethodImplOptions.Synchronized)]L.B

4 Answers

1
votes

In order for the no-variable thing to work, you'd have to either:

  • Have one auto-generated lock variable per lock block (what you did, which means that you can't have two different lock blocks locking on the same variable)
  • Use the same lock variable for all lock blocks in the same class (which means you can't have two independent things protected)

Plus, you'd also have the issue of deciding whether those should be instance-level or static.

In the end, I'm guessing the language designers didn't feel that the simplification in one specific case was worth the ambiguity introduced while reading code. Threading code (which is the reason to use locks) is already hard to write correctly and verify. Making it harder would be a not-good thing.

3
votes

The state of the lock needs to be stored. Whether or not it was entered. So that another thread that tries to enter the same lock can be blocked.

That requires a variable. Just a very simple one, an plain object is enough.

A hard requirement for such a variable is that it is created before any lock statement uses it. Trying to create it on-the-fly as you propose creates a new problem, there's a now a need to use a lock to safely create the variable so that only the first thread that enters the lock creates it and other threads trying to enter the lock are blocked until it is created. Which requires a variable. Etcetera, an unsolvable chicken-and-egg problem.

2
votes

There can be situations when you will need two different lock's, which are independent of each other. Meaning when one 'lockable' part of code is locked other 'lockable' should not be locked. That's why there is ability to provide lock objects - you can have several of them for several independent lock's

0
votes

Allowing for an implicit lock object might encourage the use of a single lock object, which is considered bad practice. By enforcing the use of an explicit lock object, the language encourages you to name the lock something useful, such as "countIncementLock".

A variable named thusly would not encourage developers to use the same lock object when performing a completely separate operation, such as writing to a stream of some kind.

Therefore, the object could be writing to a stream on one thread, while incrementing a counter on another thread, and neither of the threads would necessarily interfere with each other.

The only reason why the language wouldn't do this is because is because it would look like a good practice, but in reality would be hiding a bad practice.

Edit:

Perhaps the designers of C# did not want implicit lock variables because they thought it might encourage bad behavour.

Perhaps the designers did not think of implicit lock variables at all, because they had other more important things to think about first.

If every C# developer knew exactly what was happening when they wrote lock(), and they knew the implications, then there's no reason why it shouldn't exist, and no reason why it shouldn't work how you're suggesting.