Confusion about the lock statement in C#
Asked Answered
M

6

20

This is from MSDN: The lock keyword ensures that one thread does not enter a critical section of code while another thread is in the critical section.

Does a critical section have to be same as the critical section?

Or does it mean: The lock keyword ensures that one thread does not enter any critical section guarded by an object of code while another thread is in any critical section guarded by the same object. ?

    class Program
{
    static void Main(string[] args)
    {
        TestDifferentCriticalSections();

        Console.ReadLine();
    }

    private static void TestDifferentCriticalSections()
    {
        Test lo = new Test();

        Thread t1 = new Thread(() =>
        {
            lo.MethodA();
        });
        t1.Start();

        Thread t2 = new Thread(() =>
        {
            lo.MethodB();
        });
        t2.Start();
    }
}

public class Test
{
    private object obj = new object();

    public Test()
    { }

    public void MethodA()
    {
        lock (obj)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(500);
                Console.WriteLine("A");
            }
        }
    }

    public void MethodB()
    {
        lock (obj)
        {
            for (int i = 0; i < 5; i++)
            {
                Thread.Sleep(500);
                Console.WriteLine("B");
            }
        }
    }
}
Mannish answered 8/3, 2012 at 16:47 Comment(1)
The is more about Grammar than C#, "the" is a definitive whereas "a" is indefinite and could be refering to any section of code. englishclub.com/grammar/adjectives-determiners-the-a-an.htmYasmin
E
64

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.

Erbes answered 8/3, 2012 at 17:10 Comment(3)
As always, your answers far outstrip mine in thoroughness and accuracy. And for that, I shall shower you with upvotes.Jetty
And again, I benifit from reading one of your answers. Thanks Eric.Floater
+1 for putting active in the scary quotes. ;-) It is much needed here.Turd
L
5

You put a lock on an object. If another thread tries to access a critical section marked by that object at the same time, it will block until the lock is removed/complete.

Example:

public static object DatabaseLck= new object();

lock (DatabaseLck) {
        results = db.Query<T>(query).ToList();
     }

Or

lock (DatabaseLck) {
       results = db.Query<T>(string.Format(query, args)).ToList();
  }

Neither one of those code blocks can be run at the same time BECAUSE they use the same lock object. If you used a different lock object for each, they could run at the same time.

Loth answered 8/3, 2012 at 16:49 Comment(1)
"If another thread tries to access that object at the same time, it will block" -- Did you mean: "If another thread tries to access a critical section marked by that object at the same time, it will block"?Also
N
3

It is one and the same critical section.

lock (synclock)
{
  // the critical section protected by the lock statement
  // Only one thread can access this at any one time
}

See lock Statement on MSDN:

The lock keyword marks a statement block as a critical section by obtaining the mutual-exclusion lock for a given object, executing a statement, and then releasing the lock.


Or does it mean: The lock keyword ensures that one thread does not enter any critical section of code while another thread is in any critical section. ?

No. It does not mean that. It means the critical section protected by that lock and that lock alone.


Update, following code example:

If you use a single object to lock on, it will lock all critical sections, causing other threads to block until released. In your code example, once the lock in MethodA has been entered, all other threads reaching that lock and the lock on MethodB will block until the lock is released (this is happening because you are locking on the same object in both methods).

N answered 8/3, 2012 at 16:50 Comment(0)
W
1

It does not mean any, though you can protect 2 blocks of code from being entered by more than one thread at the same time by locking them both with the same object. This is a common paradigm -- you may want to lock your collection for both clears and writes.

Watercress answered 8/3, 2012 at 16:53 Comment(0)
K
0

No it means that another thread wont enter THE critical section protected by this lock statement.

The Critical section is only defined by the programmer, and in this case, you could replace it by : the section protected by a lock

So translation : The lock keyword ensures that one thread does not enter a section of code protected by a lock while another thread is in this section of code (protected by a lock )

Kalakalaazar answered 8/3, 2012 at 16:50 Comment(3)
Shouldn't it mean that it's any section protected by the same lock object?Frustule
No, because the object that you use in the lock is only used to guarantee the Lock.Kalakalaazar
@Kalakalaazar : Any section that locks on the same object is blocked by a lock on that object.Jetty
J
0

The critical section that it is talking about is the section guarded by the lock statements.

Any critical section that is locking on the same object will be blocked from getting access.

It is also important that your lock object be static, because the locks need to be locking (or trying to lock) on the same instance of the lock object.

Jetty answered 8/3, 2012 at 16:51 Comment(8)
Do not use double-checked locking unless you have a demonstrated performance problem with proper locking. There are about a million ways to get double-checked locking wrong and only one way to get it right; it is an insanely dangerous pattern when used carelessly. If you think you should be using double-checked locking, think again. You can probably solve your problem with (1) regular locking, (2) interlocked exchange, (3) the Lazy<T> class, (4) taking advantage of the static class initializer lock semantics.Erbes
@EricLippert : I was always under the impression that double-checked locking was a necessity. Thanks for the clarification. I've removed that comment from my answer, but I think the rest still stands scrutiny.Jetty
No, double-checked locking is a low lock technique for performance optimization, and like any low lock technique, is crazy dangerous.Erbes
@EricLippert So what is the locking technique for singletons where you check if the object is null, lock, then check if the object is null again, then instantiate? Is that still a double checked lock? Is a memory barrier necessary for that to work correctly?Jetty
@Stefan : That sounds like it could be a new useful question.Tysontyumen
@StefanH: The technique where you check for null twice is the aptly-named double-checked locking pattern, yes. As for your question about memory barriers: make your question more precise. Is a memory barrier where necessary to make it work correctly? Obviously the lock introduces a memory barrier, but the whole point of the technique is to avoid the lock.Erbes
For some additional thoughts on the dangers of double-checked locking, see https://mcmap.net/q/542833/-c-manual-lock-unlockErbes
@EricLippert The example that you referenced in that link was using double-checked locking on an object other than the singleton that is being created. I'm not sure how doing a double-checked lock could introduce the same errors that you were talking about when the object that is getting checked for null is the singleton that is getting modified inside the lock. Also, I don't quite understand the rephrasing of my question that you suggested.Jetty

© 2022 - 2024 — McMap. All rights reserved.