Why is lock(this) {...} bad?
Asked Answered
Y

18

535

The MSDN documentation says that

public class SomeObject
{
  public void SomeOperation()
  {
    lock(this)
    {
      //Access instance variables
    }
  }
}

is "a problem if the instance can be accessed publicly". I'm wondering why? Is it because the lock will be held longer than necessary? Or is there some more insidious reason?

Yacketyyak answered 30/10, 2008 at 19:19 Comment(0)
D
558

It is bad form to use this in lock statements because it is generally out of your control who else might be locking on that object.

In order to properly plan parallel operations, special care should be taken to consider possible deadlock situations, and having an unknown number of lock entry points hinders this. For example, any one with a reference to the object can lock on it without the object designer/creator knowing about it. This increases the complexity of multi-threaded solutions and might affect their correctness.

A private field is usually a better option as the compiler will enforce access restrictions to it, and it will encapsulate the locking mechanism. Using this violates encapsulation by exposing part of your locking implementation to the public. It is also not clear that you will be acquiring a lock on this unless it has been documented. Even then, relying on documentation to prevent a problem is sub-optimal.

Finally, there is the common misconception that lock(this) actually modifies the object passed as a parameter, and in some way makes it read-only or inaccessible. This is false. The object passed as a parameter to lock merely serves as a key. If a lock is already being held on that key, the lock cannot be made; otherwise, the lock is allowed.

This is why it's bad to use strings as the keys in lock statements, since they are immutable and are shared/accessible across parts of the application. You should use a private variable instead, an Object instance will do nicely.

Run the following C# code as an example.

public class Person
{
    public int Age { get; set;  }
    public string Name { get; set; }

    public void LockThis()
    {
        lock (this)
        {
            System.Threading.Thread.Sleep(10000);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var nancy = new Person {Name = "Nancy Drew", Age = 15};
        var a = new Thread(nancy.LockThis);
        a.Start();
        var b = new Thread(Timewarp);
        b.Start(nancy);
        Thread.Sleep(10);
        var anotherNancy = new Person { Name = "Nancy Drew", Age = 50 };
        var c = new Thread(NameChange);
        c.Start(anotherNancy);
        a.Join();
        Console.ReadLine();
    }

    static void Timewarp(object subject)
    {
        var person = subject as Person;
        if (person == null) throw new ArgumentNullException("subject");
        // A lock does not make the object read-only.
        lock (person.Name)
        {
            while (person.Age <= 23)
            {
                // There will be a lock on 'person' due to the LockThis method running in another thread
                if (Monitor.TryEnter(person, 10) == false)
                {
                    Console.WriteLine("'this' person is locked!");
                }
                else Monitor.Exit(person);
                person.Age++;
                if(person.Age == 18)
                {
                    // Changing the 'person.Name' value doesn't change the lock...
                    person.Name = "Nancy Smith";
                }
                Console.WriteLine("{0} is {1} years old.", person.Name, person.Age);
            }
        }
    }

    static void NameChange(object subject)
    {
        var person = subject as Person;
        if (person == null) throw new ArgumentNullException("subject");
        // You should avoid locking on strings, since they are immutable.
        if (Monitor.TryEnter(person.Name, 30) == false)
        {
            Console.WriteLine("Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string \"Nancy Drew\".");
        }
        else Monitor.Exit(person.Name);

        if (Monitor.TryEnter("Nancy Drew", 30) == false)
        {
            Console.WriteLine("Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!");
        }
        else Monitor.Exit("Nancy Drew");
        if (Monitor.TryEnter(person.Name, 10000))
        {
            string oldName = person.Name;
            person.Name = "Nancy Callahan";
            Console.WriteLine("Name changed from '{0}' to '{1}'.", oldName, person.Name);
        }
        else Monitor.Exit(person.Name);
    }
}

Console output

'this' person is locked!
Nancy Drew is 16 years old.
'this' person is locked!
Nancy Drew is 17 years old.
Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string "Nancy Drew".
'this' person is locked!
Nancy Smith is 18 years old.
'this' person is locked!
Nancy Smith is 19 years old.
'this' person is locked!
Nancy Smith is 20 years old.
Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!
'this' person is locked!
Nancy Smith is 21 years old.
'this' person is locked!
Nancy Smith is 22 years old.
'this' person is locked!
Nancy Smith is 23 years old.
'this' person is locked!
Nancy Smith is 24 years old.
Name changed from 'Nancy Drew' to 'Nancy Callahan'.
Dardani answered 30/10, 2008 at 19:19 Comment(7)
As I grok: (1) Nancy is in thread1 with lock(this). (2) SAME Nancy is in thread2 aging while still locked in thread1 - proving a locked object is not read-only. ALSO (2a) while in thread 2, this Nancy object is also locked on Name. (3) Create a DIFFERENT object with the same Name. (4) Pass into thread3 & attempt to lock with Name. (big finish) BUT "strings are immutable" meaning any object referencing the string "Nancy Drew" is looking at literally the same string instance in memory. So object2 can't get a lock on a string when object1 is locked on same valueMetabolic
Using a standard variable instead of lock(this) is standard advice; it's important to note that doing so will generally make it impossible for outside code cause the lock associated with the object to be held over between method calls. This may or may not be a good thing. There is some danger in allowing outside code to hold a lock for arbitrary duration, and classes should generally be designed so as to make such usage unnecessary, but there aren't always practical alternatives. As a simple example, unless a collection implements a ToArray or ToList method of its own...Omora
(as opposed to the `IEnumerable<T> extension methods), the only way for a thread that wants a snapshot of the collection to get one may be to enumerate it while locking out all changes. In order to do that, it must have access to a lock that is acquired by any code which would change the collection. Failure to expose the lock may make it impossible to e.g. have the program periodically perform an asynchronous snapshot of the collection (e.g. to update a collection-browsing user interface).Omora
there is the common misconception that lock(this) actually modifies the object passed as a parameter, and in some way makes it read-only or inaccessible. This is false - I believe those talks are about SyncBlock bit in CLR object so formally this is right - lock modified object itselfPalaestra
@Esteban, I absolutely love your example, it is awesome. I have a question for you. Your code of the method NameChange(..) ends with: <code> if (Monitor.TryEnter(person.Name, 10000)) { . . . } else Monitor.Exit(person.Name); </code> Should it not end with: <code> if (Monitor.TryEnter(person.Name, 10000)) { . . . Monitor.Exit(person.Name); } </code>Cart
@AviFarah, actually you would put the Monitor.Exit in a finally statement to make sure it always gets executed and you'd check against the result of the Monitor.TryEnter call. However, the point of that code in the example was simply to illustrate the locking behavior. I wanted the lock to persist and having it clean up after itself immediately would have made the demonstration void. Least, that's what I remember, this was 6 years ago!Dardani
@EstebanBrenes, I appreciate your response. I believe that you did not address my point. The last Monitor.Exit(..) in the NameChange(..) method should not be part of an else construct but part of the if (Monitor.TryEnter(..)) construct. Nevertheless, I thank you for your attentions and your article it was an eye opener.Cart
I
73

Because if people can get at your object instance (ie: your this) pointer, then they can also try to lock that same object. Now they might not be aware that you're locking on this internally, so this may cause problems (possibly a deadlock)

In addition to this, it's also bad practice, because it's locking "too much"

For example, you might have a member variable of List<int>, and the only thing you actually need to lock is that member variable. If you lock the entire object in your functions, then other things which call those functions will be blocked waiting for the lock. If those functions don't need to access the member list, you'll be causing other code to wait and slow down your application for no reason at all.

Ithaman answered 30/10, 2008 at 19:19 Comment(8)
The last paragraph of this answer is not correct. Lock does not in any way make the object inaccesible or read-only. Lock(this) does not prevent another thread from calling or modifying the object referenced by this.Dardani
It does if the other methods being called also do a lock(this). I believe that's the point he was making. Notice the "If you lock the entire object in your functionS"...Showman
@Orion: That's clearer. @Herms: Yeah, but you don't need to use 'this' to achieve that functionality, the SyncRoot property in lists serves that purpose, for example, while making it clear synchronization should be done on that key.Dardani
Re: locking "too much": It's a fine balancing act deciding what to lock. Be aware that taking a lock involves cache-flush CPU operations and is somewhat expensive. In other words: do not lock and update each individual integer. :)Planchet
The last paragraph still doesn't make sense. If you only need to restrict access to the list, why would the other functions have locks if they don't access the list?Tactic
@JoakimM.H. - let's say that you have a list, and then also an integer, and you want to restrict access to both. A single lock around both might be a problem if they don't need to be locked together (locking too much), so you could chose to have two locks insteadIthaman
That's true, but the paragraph says that the list is the only thing that needs to be locked.Tactic
"Locking too much" means expanding the access restriction to cover resources that don't need to be synchronized, rather than the alternative interpretation to engage a lock more often than necessary.Rete
I
44

Take a look at the MSDN Topic Thread Synchronization (C# Programming Guide)

Generally, it is best to avoid locking on a public type, or on object instances beyond the control of your application. For example, lock(this) can be problematic if the instance can be accessed publicly, because code beyond your control may lock on the object as well. This could create deadlock situations where two or more threads wait for the release of the same object. Locking on a public data type, as opposed to an object, can cause problems for the same reason. Locking on literal strings is especially risky because literal strings are interned by the common language runtime (CLR). This means that there is one instance of any given string literal for the entire program, the exact same object represents the literal in all running application domains, on all threads. As a result, a lock placed on a string with the same contents anywhere in the application process locks all instances of that string in the application. As a result, it is best to lock a private or protected member that is not interned. Some classes provide members specifically for locking. The Array type, for example, provides SyncRoot. Many collection types provide a SyncRoot member as well.

Injun answered 30/10, 2008 at 19:19 Comment(0)
N
36

I know this is an old thread, but because people can still look this up and rely on it, it seems important to point out that lock(typeof(SomeObject)) is significantly worse than lock(this). Having said that; sincere kudos to Alan for pointing out that lock(typeof(SomeObject)) is bad practice.

An instance of System.Type is one of the most generic, coarse-grained objects there is. At the very least, an instance of System.Type is global to an AppDomain, and .NET can run multiple programs in an AppDomain. This means that two entirely different applications could potentially cause interference in one another even to the extent of creating a deadlock if they both try to get a synchronization lock on the same global instance of System.Type.

So lock(this) isn't particularly robust form, can cause problems and should always raise eyebrows for all the reasons cited. Yet there is widely used, relatively well-respected and apparently stable code like log4net that uses the lock(this) pattern extensively, even though I would personally prefer to see that pattern change.

But lock(typeof(SomeObject)) opens up a whole new and enhanced can of worms.

For what it's worth.

Nicole answered 30/10, 2008 at 19:19 Comment(0)
G
28

...and the exact same arguments apply to this construct as well:

lock(typeof(SomeObject))
Grubman answered 30/10, 2008 at 19:19 Comment(3)
lock(typeof(SomeObject)) is actually a lot worse than lock(this) (https://mcmap.net/q/73617/-why-is-lock-this-bad).Lani
well, lock(Application.Current) is even worse then, But who would try either of these stupid things anyway? lock(this) seems logical and succint, but these other examples do not.Auberge
I don't agree that lock(this) seems particularly logical and succinct. It's an awfully coarse lock, and any other code could take a lock on your object, potentially causing interference in your internal code. Take more granular locks, and assume tighter control. What lock(this) does have going for it is that it's a lot better than lock(typeof(SomeObject)).Lani
R
10

Imagine that you have a skilled secretary at your office that's a shared resource in the department. Once in a while, you rush towards them because you have a task, only to hope that another one of your co-workers has not already claimed them. Usually you only have to wait for a brief period of time.

Because caring is sharing, your manager decides that customers can use the secretary directly as well. But this has a side effect: A customer might even claim them while you're working for this customer and you also need them to execute part of the tasks. A deadlock occurs, because claiming is no longer a hierarchy. This could have been avoided all together by not allowing customers to claim them in the first place.

lock(this) is bad as we've seen. An outside object might lock on the object and since you don't control who's using the class, anyone can lock on it... Which is the exact example as described above. Again, the solution is to limit exposure of the object. However, if you have a private, protected or internal class you could already control who is locking on your object, because you're sure that you've written your code yourself. So the message here is: don't expose it as public. Also, ensuring that a lock is used in similar scenario's avoids deadlocks.

The complete opposite of this is to lock on resources that are shared throughout the app domain -- the worst case scenario. It's like putting your secretary outside and allowing everyone out there to claim them. The result is utter chaos - or in terms of source code: it was a bad idea; throw it away and start over. So how do we do that?

Types are shared in the app domain as most people here point out. But there are even better things we can use: strings. The reason is that strings are pooled. In other words: if you have two strings that have the same contents in an app domain, there's a chance that they have the exact same pointer. Since the pointer is used as the lock key, what you basically get is a synonym for "prepare for undefined behavior".

Similarly, you shouldn't lock on WCF objects, HttpContext.Current, Thread.Current, Singletons (in general), etc. The easiest way to avoid all of this? private [static] object myLock = new object();

Remediable answered 30/10, 2008 at 19:19 Comment(3)
Actually having a private class does not prevent the issue. External code can obtain a reference to an instance of a private class ...Wiggler
@Wiggler while you're technically correct (+1 for pointing that out), my point was that you should be in control who is locking on the instance. Returning instances like that breaks that.Remediable
For locking, strings would be as bad or worse than types, exactly for the reason that they may be "pooled" or interned: shared objects, even if they weren't created with public visibility.Rete
S
3

There is very good article about it http://bytes.com/topic/c-sharp/answers/249277-dont-lock-type-objects by Rico Mariani, performance architect for the Microsoft® .NET runtime

Excerpt:

The basic problem here is that you don't own the type object, and you don't know who else could access it. In general, it's a very bad idea to rely on locking an object you didn't create and don't know who else might be accessing. Doing so invites deadlock. The safest way is to only lock private objects.

Spiros answered 30/10, 2008 at 19:19 Comment(0)
N
2

Locking on the this pointer can be bad if you are locking over a shared resource. A shared resource can be a static variable or a file on your computer - i.e. something that is shared between all users of the class. The reason is that the this pointer will contain a different reference to a location in memory each time your class is instantiated. So, locking over this in once instance of a class is different than locking over this in another instance of a class.

Check out this code to see what I mean. Add the following code to your main program in a Console application:

    static void Main(string[] args)
    {
         TestThreading();
         Console.ReadLine();
    }

    public static void TestThreading()
    {
        Random rand = new Random();
        Thread[] threads = new Thread[10];
        TestLock.balance = 100000;
        for (int i = 0; i < 10; i++)
        {
            TestLock tl = new TestLock();
            Thread t = new Thread(new ThreadStart(tl.WithdrawAmount));
            threads[i] = t;
        }
        for (int i = 0; i < 10; i++)
        {
            threads[i].Start();
        }
        Console.Read();
    }

Create a new class like the below.

 class TestLock
{
    public static int balance { get; set; }
    public static readonly Object myLock = new Object();

    public void Withdraw(int amount)
    {
      // Try both locks to see what I mean
      //             lock (this)
       lock (myLock)
        {
            Random rand = new Random();
            if (balance >= amount)
            {
                Console.WriteLine("Balance before Withdrawal :  " + balance);
                Console.WriteLine("Withdraw        : -" + amount);
                balance = balance - amount;
                Console.WriteLine("Balance after Withdrawal  :  " + balance);
            }
            else
            {
                Console.WriteLine("Can't process your transaction, current balance is :  " + balance + " and you tried to withdraw " + amount);
            }
        }

    }
    public void WithdrawAmount()
    {
        Random rand = new Random();
        Withdraw(rand.Next(1, 100) * 100);
    }
}

Here is a run of the program locking on this.

   Balance before Withdrawal :  100000
    Withdraw        : -5600
    Balance after Withdrawal  :  94400
    Balance before Withdrawal :  100000
    Balance before Withdrawal :  100000
    Withdraw        : -5600
    Balance after Withdrawal  :  88800
    Withdraw        : -5600
    Balance after Withdrawal  :  83200
    Balance before Withdrawal :  83200
    Withdraw        : -9100
    Balance after Withdrawal  :  74100
    Balance before Withdrawal :  74100
    Withdraw        : -9100
    Balance before Withdrawal :  74100
    Withdraw        : -9100
    Balance after Withdrawal  :  55900
    Balance after Withdrawal  :  65000
    Balance before Withdrawal :  55900
    Withdraw        : -9100
    Balance after Withdrawal  :  46800
    Balance before Withdrawal :  46800
    Withdraw        : -2800
    Balance after Withdrawal  :  44000
    Balance before Withdrawal :  44000
    Withdraw        : -2800
    Balance after Withdrawal  :  41200
    Balance before Withdrawal :  44000
    Withdraw        : -2800
    Balance after Withdrawal  :  38400

Here is a run of the program locking on myLock.

Balance before Withdrawal :  100000
Withdraw        : -6600
Balance after Withdrawal  :  93400
Balance before Withdrawal :  93400
Withdraw        : -6600
Balance after Withdrawal  :  86800
Balance before Withdrawal :  86800
Withdraw        : -200
Balance after Withdrawal  :  86600
Balance before Withdrawal :  86600
Withdraw        : -8500
Balance after Withdrawal  :  78100
Balance before Withdrawal :  78100
Withdraw        : -8500
Balance after Withdrawal  :  69600
Balance before Withdrawal :  69600
Withdraw        : -8500
Balance after Withdrawal  :  61100
Balance before Withdrawal :  61100
Withdraw        : -2200
Balance after Withdrawal  :  58900
Balance before Withdrawal :  58900
Withdraw        : -2200
Balance after Withdrawal  :  56700
Balance before Withdrawal :  56700
Withdraw        : -2200
Balance after Withdrawal  :  54500
Balance before Withdrawal :  54500
Withdraw        : -500
Balance after Withdrawal  :  54000
Nievesniflheim answered 30/10, 2008 at 19:19 Comment(1)
what is the thing to note in your example, like what are you show which is incorrect. its hard to spot what is wrong when you use Random rand = new Random(); nvm i think i see its the repeated BalanceMorey
I
1

In short: Communication with people that use your class.

this is (metaphorically) public, and you have no way of letting consumers know that sometimes said externally-accessible thing is being used as a mutex. A user might expect that it's safe to use your object reference as a mutex, but it might not be if you are. If you, instead, use a private field as a mutex, then you can safely assume that people outside your class cannot (although this might make your class larger if you'd need to add a private field specifically to use it as a lock... if that matters).

I mean, they can take a lock on it if they use reflection, but then you can chuckle as you close the bug report. They did it to themselves at that point.

Irby answered 30/10, 2008 at 19:19 Comment(0)
N
1

You can establish a rule that says that a class can have code that locks on 'this' or any object that the code in the class instantiates. So it's only a problem if the pattern is not followed.

If you want to protect yourself from code that won't follow this pattern, then the accepted answer is correct. But if the pattern is followed, it's not a problem.

The advantage of lock(this) is efficiency. What if you have a simple "value object" that holds a single value. It's just a wrapper, and it gets instantiated millions of times. By requiring the creation of a private sync object just for locking, you've basically doubled the size of the object and doubled the number of allocations. When performance matters, this is an advantage.

When you don't care about number of allocations or memory footprint, avoiding lock(this) is preferable for the reasons indicated in other answers.

Naphtha answered 30/10, 2008 at 19:19 Comment(0)
H
1

Here's a much simpler illustration (taken from Question 34 here) why lock(this) is bad and may result in deadlocks when consumer of your class also try to lock on the object. Below, only one of three thread can proceed, the other two are deadlocked.

class SomeClass
{
    public void SomeMethod(int id)
    {
        **lock(this)**
        {
            while(true)
            {
                Console.WriteLine("SomeClass.SomeMethod #" + id);
            }
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        SomeClass o = new SomeClass();

        lock(o)
        {
            for (int threadId = 0; threadId < 3; threadId++)
            {
                Thread t = new Thread(() => {
                    o.SomeMethod(threadId);
                        });
                t.Start();
            }

            Console.WriteLine();
        }

To work around, this guy used Thread.TryMonitor (with timeout) instead of lock:

            Monitor.TryEnter(temp, millisecondsTimeout, ref lockWasTaken);
            if (lockWasTaken)
            {
                doAction();
            }
            else
            {
                throw new Exception("Could not get lock");
            }

https://blogs.appbeat.io/post/c-how-to-lock-without-deadlocks

Hebdomad answered 30/10, 2008 at 19:19 Comment(2)
As far as I see, when I replace the lock(this) with a lock on a private instance member of SomeClass, I still get the same deadlock. Also, if the lock in the main class is done on another private instance member of Program, the same lock occurs. So, not sure if this answer is not misleading and incorrect. See that behaviour in here: dotnetfiddle.net/DMrU5hCutwork
while (true); - is realy reason of deadlock))))Legend
D
1

Please refer to the following link which explains why lock (this) is not a good idea.

https://learn.microsoft.com/en-us/dotnet/standard/threading/managed-threading-best-practices

So the solution is to add a private object, for example, lockObject to the class and place the code region inside the lock statement as shown below:

lock (lockObject)
{
...
}
Downhaul answered 30/10, 2008 at 19:19 Comment(0)
A
1

Here is some sample code that is simpler to follow (IMO): (Will work in LinqPad, reference following namespaces: System.Net and System.Threading.Tasks)

Something to remember is that lock(x) basically is syntactic sugar and what it does is to use Monitor.Enter and then uses a try, catch, finally block to call Monitor.Exit. See: https://learn.microsoft.com/en-us/dotnet/api/system.threading.monitor.enter (remarks section)

or use the C# lock statement (SyncLock statement in Visual Basic), which wraps the Enter and Exit methods in a try…finally block.

void Main()
{
    //demonstrates why locking on THIS is BADD! (you should never lock on something that is publicly accessible)
    ClassTest test = new ClassTest();
    lock(test) //locking on the instance of ClassTest
    {
        Console.WriteLine($"CurrentThread {Thread.CurrentThread.ManagedThreadId}");
        Parallel.Invoke(new Action[]
        {
            () => {
                //this is there to just use up the current main thread. 
                Console.WriteLine($"CurrentThread {Thread.CurrentThread.ManagedThreadId}");
                },
            //none of these will enter the lock section.
            () => test.DoWorkUsingThisLock(1),//this will dead lock as lock(x) uses Monitor.Enter
            () => test.DoWorkUsingMonitor(2), //this will not dead lock as it uses Montory.TryEnter
        });
    }
}

public class ClassTest
{
    public void DoWorkUsingThisLock(int i)
    {
        Console.WriteLine($"Start ClassTest.DoWorkUsingThisLock {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
        lock(this) //this can be bad if someone has locked on this already, as it will cause it to be deadlocked!
        {
            Console.WriteLine($"Running: ClassTest.DoWorkUsingThisLock {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
        }
        Console.WriteLine($"End ClassTest.DoWorkUsingThisLock Done {i}  CurrentThread {Thread.CurrentThread.ManagedThreadId}");
    }

    public void DoWorkUsingMonitor(int i)
    {
        Console.WriteLine($"Start ClassTest.DoWorkUsingMonitor {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
        if (Monitor.TryEnter(this))
        {
            Console.WriteLine($"Running: ClassTest.DoWorkUsingMonitor {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
            Monitor.Exit(this);
        }
        else
        {
            Console.WriteLine($"Skipped lock section!  {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
        }

        Console.WriteLine($"End ClassTest.DoWorkUsingMonitor Done {i} CurrentThread {Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine();
    }
}

Output

CurrentThread 15
CurrentThread 15
Start ClassTest.DoWorkUsingMonitor 2 CurrentThread 13
Start ClassTest.DoWorkUsingThisLock 1 CurrentThread 12
Skipped lock section!  2 CurrentThread 13
End ClassTest.DoWorkUsingMonitor Done 2 CurrentThread 13

Notice that Thread#12 never ends as its dead locked.

Abracadabra answered 30/10, 2008 at 19:19 Comment(4)
seems that the second DoWorkUsingThisLock thread is not necessary to illustrate the issue?Overdress
dont you mean the outter lock in main, one thread would simply wait for the other to complete? which then would invalidate the Parallel... i feel we need better real world examples..Morey
@Seabizkit, updated the code to make it a little clearer. The Parallel is there just to create a new thread and run the code asynchronously. In reality, the 2nd thread could have been invoked by any number of ways (button click, separate request, etc).Abracadabra
If someone else hold the lock inside ‹DoThisInsideThisLock› method, it will be released after 1sec. It shouldn't create a deadlock,should it?Witte
P
1

Because any chunk of code that can see the instance of your class can also lock on that reference. You want to hide (encapsulate) your locking object so that only code that needs to reference it can reference it. The keyword this refers to the current class instance, so any number of things could have reference to it and could use it to do thread synchronization.

To be clear, this is bad because some other chunk of code could use the class instance to lock, and might prevent your code from obtaining a timely lock or could create other thread sync problems. Best case: nothing else uses a reference to your class to lock. Middle case: something uses a reference to your class to do locks and it causes performance problems. Worst case: something uses a reference of your class to do locks and it causes really bad, really subtle, really hard-to-debug problems.

Peavy answered 30/10, 2008 at 19:30 Comment(0)
A
-1

There will be a problem if the instance can be accessed publicly because there could be other requests that might be using the same object instance. It's better to use private/static variable.

Aceae answered 30/10, 2008 at 19:19 Comment(1)
Not sure what that adds to the man, detailed answers already existing which say the same thing.Crazed
H
-2

lock(this) is perfectly fine, implementation of this belongs to you. What is not fine is locking some object that doesn't belong to you, that may lock itself. So it is a problem of someone who uses your code that he uses it incorrectly.

However, if someone makes it on purpose, it gives him more flexibility.

So follow KISS, lock this and don't over-engineer - don't solve issue that doesn't exist.

Hunfredo answered 30/10, 2008 at 19:19 Comment(0)
F
-2

Sorry guys but I can't agree with the argument that locking this might cause deadlock. You are confusing two things: deadlocking and starving.

  • You cannot cancel deadlock without interrupting one of the threads so after you get into a deadlock you cannot get out
  • Starving will end automatically after one of the threads finishes its job

Here is a picture which illustrates the difference.

Conclusion
You can still safely use lock(this) if thread starvation is not an issue for you. You still have to keep in mind that when the thread, which is starving thread using lock(this) ends in a lock having your object locked, it will finally end in eternal starvation ;)

Flattop answered 30/10, 2008 at 19:19 Comment(3)
There is a difference but it's entirely irrelevant to this discussion. And the first sentence of your conclusion is flat wrong.Collencollenchyma
To be clear: I'm not defending lock(this) - this kind of code is simply wrong. I just think calling it deadlocking is a little abusive.Flattop
Link to image is no longer available. :( Any chance you can re-reference it? ThxIlo
H
-3

Here is why it is not recommended.

Consider you wrote a class (SomeClass in this example) and consumer of your class (a developer named "John") wants to acquire a lock over an instance of your class (someObject in this example). He gets a lock over instance someObject and inside this lock he calls a method of that instance (SomeMethod()) which internally acquires a lock over the exact same instance.
So far so good. Now, consider that John wants to call it through a Task<>, you see a deadlock happen because o object is acquired by another process while SomeMethod() method needs to acquire the exact same instance in another process.

  • Although John applied a bad practice of using an instance of a class as a lock-object, but we (as the developer of a classlibrary SomeClass) should deter such situation simple by not using this as a lock-object in our class.

  • Instead, we should declare a simple private field and use that as our lock-object.

     using System;
     using System.Threading;
     using System.Threading.Tasks;
    
     class SomeClass
     {
         public void SomeMethod()
         {
             //NOTE: Locks over an object that is already locked by the caller.
             //      Hence, the following code-block never executes.
             lock (this)
             {
                 Console.WriteLine("Hi");
             }
         }
     }
    
     public class Program
     {
         public static void Main()
         {
             SomeClass o = new SomeClass();
    
             lock (o)
             {
                 Task.Run(() => o.SomeMethod()).Wait();
             }
    
             Console.WriteLine("Finish");
         }
     }
    
Halter answered 30/10, 2008 at 19:19 Comment(7)
If I understood your program and explanation correctly,Your scenario might give rise to nested lock which is allowed in .NETWitte
No, actually I explained how two bad practices could cause deadlock. lock(this) tries to acquire a lock over this while this token/object is already acquired by lock(o). Although these two lock() look nested but they aren't. Because they are contesting to get the same object. Attention: o and this are both referring to the same object. Furthermore, we cannot prevent John from applying bad practices to his code, still we can prevent a potential dead-lock scenario.Halter
Are you suggesting they are not necessarily nested because lock(o) and lock(this) are being called from different threads?Witte
No, lock(o) and lock(this) are not nested because they are contesting over one instance. In this context o and this are referring to a same object. That's why they are not nested. object foo = new Object(); object bar = foo; lock(foo){lock(bar){}} Here, foo and bar are referring to a same instance of object causing deadlock. That is the simplified version of what may happens in reality.Halter
I get the idea. I am considering a hypothetical situation from a single thread's perspective. If from a single thread, 2 different methods using lock(this) invoke one from inside another will not create deadlock but result in nested lock?Witte
object foo = new Object(); object bar = foo; lock(foo) { lock(bar){} } ------ Running this code of yours, calling from a thread which already obtained [foo] as lock will not cause deadlock while attempting lock(bar)Witte
@Witte sorry for late answering. Yes you right; but if you run it from inside a Task<>, you see there is a race condition of two different processes over one resource. Please see my updated answer.Halter

© 2022 - 2024 — McMap. All rights reserved.