web-dev-qa-db-ja.com

変更するオブジェクトをロックするのはなぜ悪い習慣なのですか?

次のコードのようにロックを使用するのはなぜ悪い習慣なのですか、これは this SO here 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 に対する回答は、それが悪い習慣であることを述べていますが、彼らはその理由を述べていません

注:コードの有用性は無視してください。これは単なる例であり、まったく役に立たないことを知っています

45
Vamsi

C#言語リファレンスから here

一般に、パブリックタイプ、またはコードの制御が及ばないインスタンスをロックすることは避けてください。共通の構造体lock (this)lock (typeof (MyType))、およびlock ("myLock")は、このガイドラインに違反しています。

lock (this)は、インスタンスがパブリックにアクセスできる場合の問題です。

MyTypeが一般公開されている場合、lock (typeof (MyType))は問題です。

lock("myLock")は、同じ文字列を使用するプロセス内の他のコードが同じロックを共有するため、問題になります。

ベストプラクティスは、ロックするプライベートオブジェクト、またはすべてのインスタンスに共通のデータを保護するプライベートスタティックオブジェクト変数を定義することです。

あなたの場合、上記のガイダンスを読んで、変更するコレクションをロックすることは悪い習慣であることを示唆しています。たとえば、次のコードを記述した場合:

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

...その後、あなたのロックは価値がなくなります。これらの理由により、ロックには専用のobject変数を使用することをお勧めします。

これは、投稿したコードを使用した場合にアプリケーションがbreakすることを意味しないことに注意してください。 「ベストプラクティス」は通常、技術的に弾力性のある、繰り返しが容易なパターンを提供するために定義されます。つまり、ベストプラクティスに従って、専用の「ロックオブジェクト」がある場合、ever壊れたlock-を書き込む可能性はほとんどありません。ベースのコード。その場合、ベストプラクティスに従わないと、おそらく100回に1回は、簡単に回避できる問題に悩まされることになります。

さらに(そしてより一般的に)、ベストプラクティスを使用して記述されたコードは通常、より簡単に変更できます。これは、予期しない副作用に対する警戒心が軽減されるためです。

45
Dan Puzey

誰かが同じオブジェクト参照を使用して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
            }
        });
    }
}

次に、お客様にとって、デバッグが困難な素敵なデッドロックが発生する可能性があります。2つのスレッドはそれぞれ、otherProductListインスタンスがロックされなくなるのを待機しています。

私は同意します、このシナリオは起こりそうにありませんが、それはロックされた参照が、所有していないコードの一部に表示されている場合、何らかの方法で、最終的なコードが壊れる可能性があることを示しています

4
ken2k