2

I was looking at the answer for not using "this" in the lock, if the instance is publicly accessible. I tried below example , I was thinking Method1 will be not called since lock is already acquired in Main method on the instance. But Method1 is called Method2 wait indefinitely. Explanation for the same will be appreciated .

class Program
{
    static void Main(string[] args)
    {
        Tracker tracker = new Tracker();
        lock (tracker)
        {
            Parallel.Invoke(() => tracker.Method1(),
                () => tracker.Method2());
        }
    }
}

class Tracker
{
    private int number = 6;
    public void Method1()
    {
        lock (this)
        {
            number *= 5;
            Console.WriteLine("Method1: " + number);
            number /= 4;
            Console.WriteLine("Method1: " + number);
        }
    }

    public void Method2()
    {
        lock (this)
        {
            number *= 3;
            Console.WriteLine("Method2: " + number);
            number /= 2;
            Console.WriteLine("Method2: " + number);
        }
    }
}
Liam
  • 27,717
  • 28
  • 128
  • 190
  • 1
    [That's not how you supposed to use lock](http://stackoverflow.com/questions/6029804/how-does-lock-work-exactly). You need a synchonisation object, don't use this. That said you code is very confusing and it's unclear what your actually trying to do. There isn't much point running something in parallel if you going to lock all the time. You may as well run it synchonously. – Liam Oct 17 '16 at 15:05
  • Thanks for the answer. I was just trying to understand why one should not use lock (this). Wrote this example for demoing purpose and was expecting method1 will be never called. – raghavendrasri Oct 17 '16 at 15:40

2 Answers2

6

It's not impossible to lock on an object that's publicly accessible, it's just a really bad idea as it makes understanding the synchronization of an object much harder. The general practice is to never lock on an object that is ever accessible outside of the scope of the current type because it makes it very easy to keep track of all of the possible places where the lock can be acquired, which makes it much easier to diagnose potential problems with the synchronization and to understand what, exactly is being synchronized, when looking through the code.

When you're synchronizing on an object that is publicly exposed it will work, it just means that if you run into a problem you have an enormous space to look through to try to figure out what the problematic interactions are. It also makes it much easier to end up with a deadlock when lots of different code in very different places is all interacting with an object being locked on.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • The question is how `Method1` gets inside of lock block, if it is called from `lock` on the same object in `Main`. – Qwertiy Oct 17 '16 at 15:38
  • @Qwertiy The question doesn't actually say that, but if that's what you think the OP is actually interested in, the other answers covers that. – Servy Oct 17 '16 at 15:42
3

From the documentation of Parallel.Invoke:

Executes each of the provided actions, possibly in parallel.

So here is what's happening:

  • Your first action runs in the same thread as the main function call,
  • Locks in C# are re-entant, so Method1 can re-acquire the lock.

The following code prints the thread id-s to prove that Main and Method1 runs in the same thread.

class Program
{
    static void Main(string[] args)
    {
        Tracker tracker = new Tracker();
        Console.WriteLine("Main TID: " + Thread.CurrentThread.ManagedThreadId);
        lock (tracker)
        {
            Console.WriteLine("Main Acquired");
            Parallel.Invoke(() => tracker.Method1(),
                () => tracker.Method2());
        }
    }
}

class Tracker
{
    private int number = 6;
    public void Method1()
    {
        Console.WriteLine("Method1 TID: " + Thread.CurrentThread.ManagedThreadId);
        lock (this)
        {
            Console.WriteLine("Method1 Acquired");
        }
    }

    public void Method2()
    {
        Console.WriteLine("Method2 TID: " + Thread.CurrentThread.ManagedThreadId);
        lock (this)
        {
            Console.WriteLine("Method2 Acquired");
        }
    }
}

Outputs

Main TID: 1
Main Acquired
Method1 TID: 1
Method1 Acquired
Method2 TID: 3

And then it hangs. Thread ID-s show that Method1 runs in the same thread as Main.

Community
  • 1
  • 1
Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97