首页 文章

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

提问于
浏览
39

为什么在下面的代码中使用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提到这是不好的做法,但他们没有说明原因

Note: 请忽略代码的用处,这只是为了示例目的,我知道它根本没用

2 回答

  • 2

    从C#语言参考here

    通常,避免锁定公共类型或超出代码控制范围的实例 . 常见的构造锁(this),lock(typeof(MyType))和lock(“myLock”)违反了该指南:如果可以公开访问实例,则lock(this)是一个问题 . 如果MyType可公开访问,则lock(typeof(MyType))是一个问题 . lock(“myLock”)是一个问题,因为进程中使用相同字符串的任何其他代码将共享相同的锁 . 最佳做法是定义要锁定的私有对象,或私有静态对象变量以保护所有实例共有的数据 .

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

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

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

    请注意,这并不意味着如果您使用您发布的代码,您的应用程序将会中断 . "Best practices"通常被定义为提供技术上更具弹性的易于重复的模式 . 也就是说,如果你遵循最佳实践并拥有一个专门的"lock object,",你几乎不可能写出破碎的基于 lock 的代码;如果你不被一个容易避免的问题所困扰 .

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

  • 38

    确实可能不是一个好主意,因为如果其他人使用相同的对象引用来执行 lock ,则可能会出现死锁 . If there is a chance your locked object is accessible outside your own 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);
                    }
                });
            }
        }
    }
    

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

    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 实例不再被锁定 .

    我同意,这种情况不太可能发生,但它说明了 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 .

相关问题