为什么锁定我们要改变的对象是一种不好的做法?

时间:2012-08-02 10:03:27

标签: c# multithreading locking

为什么在下面的代码中使用lock是一种不好的做法,我认为根据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());
            }
        });
}

there的答案提到这是不好的做法,但他们没有说明原因

注意:请忽略代码的用处,这只是为了示例目的而且我知道它没有用处

2 个答案:

答案 0 :(得分:41)

来自C#语言参考here

  

通常,请避免锁定公共类型或超出代码控制范围的实例。常见构造lock (this)lock (typeof (MyType))lock ("myLock")违反了此准则:

     如果可以公开访问实例,则

lock (this)会出现问题。

     如果lock (typeof (MyType))可公开访问,则

MyType会出现问题。

     

lock("myLock")是一个问题,因为该过程中的任何其他代码   使用相同的字符串,将共享相同的锁。

     

最佳做法是定义要锁定的私有对象或私有对象   静态对象变量,用于保护所有实例共有的数据。

在您的情况下,我会阅读上述指南,建议锁定您将要修改的集合是不好的做法。例如,如果您编写了此代码:

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

...那么你的锁将毫无价值。出于这些原因,建议使用专用的object变量进行锁定。

请注意,如果您使用发布的代码,这并不意味着您的应用程序将中断。 &#34;最佳实践&#34;通常被定义为提供易于重复的模式,这些模式在技术上更具弹性。也就是说,如果你遵循最佳实践并拥有一个专门的锁定对象,&#34;你不太可能永远编写基于lock的破解代码;如果你不遵循最佳实践,那么也许一百次,你会被一个容易避免的问题所困扰。

此外(更一般地说),使用最佳实践编写的代码通常更容易修改,因为您可以避免意外的副作用。

答案 1 :(得分:2)

确实可能不是一个好主意,因为如果其他人使用相同的对象引用来执行lock,则可能会出现死锁。 如果您的锁定对象有可能在您自己的代码之外访问,那么其他人可能会破坏您的代码。

根据您的代码设想以下示例:

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);
                }
            });
        }
    }
}

现在您编译库,将其发送给客户,此客户在其代码中写入:

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
            }
        });
    }
}

然后,您的客户可能会有一个难以调试的难以解决的死锁,两个用过的线程中的每一个都等待otherProductList实例不再被锁定。

我同意,这种情况不太可能发生,但它说明了如果您的锁定引用在您不拥有的代码中可见,则以任何可能的方式,那么最终代码有可能被打破