C# lock statement, what object to lock on?

70,250

Solution 1

How and what you lock on depends upon what you're doing.

Let's say that you're working with a device of some kind - say a coffee maker. You might have a class that looks like this:

public CoffeeMaker {
    private IntPtr _coffeeHandle;
    private Object _lock = new Object();
}

In this case, you are protecting access to the _coffeeHandle - a pointer/handle to a real physical device, so this is pretty easy:

public int AvailableCups {
    get {
        lock (_lock) {
            return GetAvailableCups(_coffeeHandle); // P/Invoked
        }
    }
}

public void Dispense(int nCups)
{
    lock (_lock) {
        int nAvail = GetAvailableCups(_coffeeHandle);
        if (nAvail < nCups) throw new CoffeeException("not enough coffee.");
        Dispense(_coffeeHandle, nCups); // P/Invoked
    }
 }

So if I'm running a multithreaded app, I (probably) don't want to read the number of cups that are available while I'm dispensing (maybe it's a hardware error). By protecting accesses to the handle, I can ensure that. Also, I can't be asked to dispense while I'm already dispensing - that would be bad, so that's protected too. Finally, I don't dispense unless I have enough coffee available and you notice that I don't use my public property to check that - this way the action of ensuring there's enough coffee and dispensing are tied together. The magic word is atomic - they can't be cut apart without creating issues.

You use a static object as a lock if you have one and only one instance of a resource that needs protecting. Think, "do I have a singleton?" and that will be a guideline for when you might need a static lock. For example, let's say that CoffeeMaker has a private constructor. Instead, you have a factory method that constructs coffee machines:

static Object _factLock = new Object();

private CoffeeMaker(IntPtr handle) { _coffeeHandle = handle; }

public static CoffeeMaker GetCoffeeMaker()
{
    lock (_factLock) {
        IntPtr _handle = GetCoffeeMakerHandle(); // P/Invoked
        if (_handle == IntPtr.Zero) return null;
        return new CoffeeMaker(_handle);
    }
 }

Now in this case, it feels like CoffeeMaker should implement IDisposable so that handle gets taken care of, because if you don't release it then somebody might not be getting their coffee.

There are a few problems though - maybe if there's not enough coffee, we should make more - and that takes a long time. Heck - dispensing coffee takes a long time, which is why we're careful to protect our resources. Now you're thinking that really all this coffee maker stuff should be in a thread of its own and that there should be an event that gets fired when the coffee is done, and then it starts to get complicated and you understand the importance of knowing what you're locking on and when so that you don't block making coffee because you asked how many cups are there.

And if the words "deadlock", "atomic", "monitor", "wait", and "pulse" all sound foreign to you, you should consider reading up on multiprocessing/multithreading in general and see if you can solve the fair barbershop problem or the dining philosophers problem, both quintessential examples of resource contention.

Solution 2

1) your code is incomplete. You always lock around a certain (shared) resource. The anyObject should have a close 1-1 correspondence in lifetime with that shared object.

For instance:

a) the simple but most direct pattern:

List<MyClass> sharedList = ...;
...
lock (sharedList) { sharedList.Add(item); }

there is a drawback in this pattern: what if other code also locks on sharedList for other reasons? Usually not a practical problem, but it is the reason that the recommended pattern is (b):

List<MyClass> sharedList = ...;
private object listLock = new object();
...
lock (listLock) { sharedList.Add(item); }

Or, when the shared object is static (c) :

static List<MyClass> sharedList = ...;
private static object listLock = new object();
...
lock (listLock) { sharedList.Add(item); }

2) The threads alternate setting readerFlag to true or false so the try/catch blocks will be entered. The synchronization is done with Monitor.Pulse() and .Wait(). Note that Wait() will yield the lock for the duration s there is no deadlock.

Solution 3

1: the object you use defines / is-defined-by the lock granularity you are trying to enforce. If is is "anything calling against the current instance", then a private readonly object syncLock = new object() would be reasonable. If it is "any code, regardless of the instance" (static, in particular), then private readonly static object syncLock = new object(). Sometimes there is an obvious "thing" you are trying to protect that will also serve: a list, a queue, etc. The main wrong decisions are: this, typeof(...), any string, any value-type that you are boxing for each lock, and anything that you have leaked outside of the instance.

2: Monitor.Wait releases the locks from the current thread, waiting for either a "pulse" or a timeout, at which point it wakes up and joins the queue to regain to locks it had (note the "s" there is for re-entrancy). That means that two threads can use a Monitor to signal between themselves, by pulsing and waiting.

3: unrelated; but basically "check a flag periodically, and when being pulsed"

Solution 4

According the MSDN documentation:

The argument provided to the lock keyword ... is used to define the scope of the lock. ...Strictly speaking, the object provided is used solely to uniquely identify the resource being shared among multiple threads, so it can be an arbitrary class instance. In practice, however, this object usually represents the resource for which thread synchronization is necessary.

In my case, I have passed the exact static object that I have needed to change.

Share:
70,250
user1885498
Author by

user1885498

Updated on July 08, 2022

Comments

  • user1885498
    user1885498 almost 2 years

    I have 3 questions that I need help with.

    1. What are the correct objects/references to be passed as lock statement parameter? I've seen a lot of sample codes and I noticed that the objects/references passed in could possibly be non related to the current class or any other class in the program as long as the access modifier static is non public? E.g.:

      private Object anyObj = new Object();
      lock(anyObj){.....}
      
      private static readonly object Locker = new object();
      lock(Locker){.....}
      

      It just doesn't make sense to me.

    2. I found a sample code in MSDN about multi threading that uses lock statements too. In the sample there are two try/catch blocks with the Monitor.Wait() within it. If I understand the logic correctly, the readerFlag will forbid the program to ever enter the try/catch block at all.
      The code is example 2 from here:
      http://msdn.microsoft.com/en-us/library/aa645740(v=vs.71).aspx

    3. How do I run a thread that runs in the background as long as the Windows Form is active?