51

Why is it a bad practice to use lock as in the following code, I'm assuming this is a bad practice based on the answers in this SO question here

private void DoSomethingUseLess()
{
    List<IProduct> otherProductList = new List<IProduct>();
    Parallel.ForEach(myOriginalProductList, product =>
        {
           //Some code here removed for brevity
           //Some more code here :)
            lock (otherProductList)
            {
                otherProductList.Add((IProduct)product.Clone());
            }
        });
}

The answers over there mentions that it is bad practice , but they don't say why

Note: Please ignore the usefulness of the code, this is just for example purpose and i know it is not at all useful

Community
  • 1
  • 1
Vamsi
  • 4,237
  • 7
  • 49
  • 74
  • 2
    With the code as you have it, `otherProductList` is local to this code, so it wouldn't be a problem to lock on it (as no one else can see it). Problems only (potentially) come when you lock on something that 'other code' can also see. – AakashM Aug 02 '12 at 10:18
  • @AakashM: not true. The code is more fragile than it should be, and you can't guarantee that `otherProductList` isn't passed outside of the function or even outside of the class. If the "code removed for brevity" includes a call to `SomeRandomClass.SomeMethod(otherProductList)` then you've given the object to someone else. – Dan Puzey Aug 02 '12 at 10:23
  • 2
    @DanPuzey hence my caveat 'With the code as you have it'. I *can* guarantee that, because I can see the entire scope and every usage of the variable in question. If the comments were replaced by code, then indeed things might be different; but it's always true that different questions might have different answers... – AakashM Aug 02 '12 at 10:25
  • @AakashM: the code as written has *explicitly* omitted part of that code. I don't think you can given an answer with an implicit assumption about what's been missed. – Dan Puzey Aug 02 '12 at 10:28
  • That's not a bad practise, it's just wrong. You're not locking on anything that can be accessed by more than one thread and is therefore pointless – Peter Ritchie Aug 02 '12 at 14:27
  • @PeterRitchie may i know why, b'coz i was thinking `otherProductList` will be accessed by more than one thread – Vamsi Aug 03 '12 at 04:19

2 Answers2

50

From the C# language reference here:

In general, avoid locking on a public type, or instances beyond your code's control. The common constructs lock (this), lock (typeof (MyType)), and lock ("myLock") violate this guideline:

lock (this) is a problem if the instance can be accessed publicly.

lock (typeof (MyType)) is a problem if MyType is publicly accessible.

lock("myLock") is a problem because any other code in the process using the same string, will share the same lock.

Best practice is to define a private object to lock on, or a private static object variable to protect data common to all instances.

In your case, I would read the above guidance as suggesting that locking on the collection you will be modifying is bad practise. For example, if you wrote this code:

lock (otherProductList) 
{
    otherProductList = new List<IProduct>(); 
}

...then your lock will be worthless. For these reasons it's recommended to use a dedicated object variable for locking.

Note that this doesn't mean your application will break if you use the code you posted. "Best practices" are usually defined to provide easily-repeated patterns that are more technically resilient. That is, if you follow best practice and have a dedicated "lock object," you are highly unlikely to ever write broken lock-based code; if you don't follow best practice then, maybe one time in a hundred, you'll get bitten by an easily-avoided problem.

Additionally (and more generally), code written using best practices is typically more easily modified, because you can be less wary of unexpected side-effects.

Teoman shipahi
  • 47,454
  • 15
  • 134
  • 158
Dan Puzey
  • 33,626
  • 4
  • 73
  • 96
  • That's a great answer, is there any other scenario, where this might cause an issue, please check i have updated the question – Vamsi Aug 02 '12 at 10:15
  • 5
    ok you're quoting MSDN, and repeating it is bad practice to lock publicly accessible objects, but still not telling *why* exactly this is a bad practice. E.g. the risk for deadlocks (answer of @ken2k) is another reason. – jeroenh Aug 02 '12 at 10:21
  • 1
    Well, a particular example with the code you've posted would be that, although your variable is private, there's nothing to stop you passing that list to a method in another class. If that class were to `lock` on the list that you passed as a parameter (perhaps there is some other developer writing poor code in another component), it could affect the operation and/or performance of your code. This ties in with my final point: best-practise code is more easily modified, because the internal "locking object" would never be passed elsewhere. – Dan Puzey Aug 02 '12 at 10:22
  • 1
    @jeroenh: Yes, the risk for deadlocks is the only reason. Cause to find them later is a burden. – Oliver Aug 02 '12 at 10:23
  • 2
    @jeroenh: risk of deadlock is a risk in *any* threaded code. That doesn't necessarily increase just because you're locking on the object being modified. The code in the original post is no more deadlock-prone than it would be using a dedicated object - but following the best practise helps you *keep* it that way. – Dan Puzey Aug 02 '12 at 10:26
  • @Oliver: deadlocks are not the only reason. If you called `otherProductList = new List()` (as in my post) then you'd get no deadlock, but you'd get broken code. – Dan Puzey Aug 02 '12 at 10:26
5

It might be not a good idea indeed, because if someone else uses the same object reference to do a lock, you could have a deadlock. If there is a chance your locked object is accessible outside your own code, then someone else could break your code.

Imagine the following example based on your code:

namespace ClassLibrary1
{
    public class Foo : IProduct
    {
    }

    public interface IProduct
    {
    }

    public class MyClass
    {
        public List<IProduct> myOriginalProductList = new List<IProduct> { new Foo(), new Foo() };

        public void Test(Action<IEnumerable<IProduct>> handler)
        {
            List<IProduct> otherProductList = new List<IProduct> { new Foo(), new Foo() };
            Parallel.ForEach(myOriginalProductList, product =>
            {
                lock (otherProductList)
                {
                    if (handler != null)
                    {
                        handler(otherProductList);
                    }

                    otherProductList.Add(product);
                }
            });
        }
    }
}

Now you compile your library, send it to a customer, and this customer writes in his code:

public class Program
{
    private static void Main(string[] args)
    {
        new MyClass().Test(z => SomeMethod(z));
    }

    private static void SomeMethod(IEnumerable<IProduct> myReference)
    {
        Parallel.ForEach(myReference, item =>
        {
            lock (myReference)
            {
                // Some stuff here
            }
        });
    }
}

Then there could be a nice hard-to-debug deadlock for your customer, each of two used thread waiting for the otherProductList instance to be not locked anymore.

I agree, this scenario is unlikely to happen, but it illustrates that if your locked reference is visible in a piece of code you do not own, by any possible way, then there's a possibility for the final code to be broken.

ken2k
  • 48,145
  • 10
  • 116
  • 176
  • No, it is not possible, according to your code `myOriginalProductList`is only accessible outside not `otherProductList`, plz check – Vamsi Aug 02 '12 at 10:20
  • @VamsiKrishna you're right, I misread the question. I updated the example to demonstrates a possible deadlock – ken2k Aug 02 '12 at 13:18
  • Wow that's a great one, i don't know that it could happen, +1 – Vamsi Aug 02 '12 at 13:55
  • ahh! I was just wondering how could this scenario not occur, if we used a separate object just for locking – Vamsi Aug 03 '12 at 04:15
  • 1
    If the client code relies upon a collection not changing while it does certain things, then it must acquire whatever lock would be used by anyone modifying the collection. Clients should avoid holding locks while running unknown code, but if e.g. a client wants to take a snapshot of a collection used by other threads, I know of no alternative to acquiring the lock, enumerating the collection, and releasing the lock. If `IEnumerable` included a `Snapshot` method, having that method use the lock internally would be better than exposing it to outside code, but without such a method... – supercat May 06 '14 at 18:15
  • 1
    ...what alternative is there to having outside code hold a lock during enumeration? – supercat May 06 '14 at 18:15