31

lock(this)別のスレッドが同じキーをロックしてデッドロックを引き起こす可能性があるため、 , lock(typeof(MyType)),lock("a string")はすべて悪い習慣であるという記事や投稿をいくつか読みました。この問題を理解するために、デッドロックを説明するサンプル コードを作成しようとしましたが、これについて理解することができませんでした。

誰かがこの古典的な問題を説明する簡潔なコードを書くことができますか? 短くしてください。コードを小さなチャンクでしか消化できません。

編集: lassevkはそれをうまくまとめていると思います。本当の問題は、ロックを制御できなくなったことです。これが発生すると、ロックが呼び出される順序を制御できなくなり、潜在的なデッドロック状態が発生する可能性があります。

lock(this)lock(typeof(MyType))などはすべて、制御できないロックを選択した状況です。

4

6 に答える 6

34

デッドロックは、複数のロックがある場合にのみ発生します。両方のスレッドが他方が必要とするリソースを保持している状況が必要です (つまり、少なくとも 2 つのリソースが必要であり、2 つのスレッドがそれらを異なる順序で取得しようとする必要があります)。

簡単な例:

// thread 1
lock(typeof(int)) {
  Thread.Sleep(1000);
  lock(typeof(float)) {
    Console.WriteLine("Thread 1 got both locks");
  }

}

// thread 2
lock(typeof(float)) {
  Thread.Sleep(1000);
  lock(typeof(int)) {
    Console.WriteLine("Thread 2 got both locks");
  }
}

両方のスレッドが互いに 1 秒以内に開始されると仮定すると、誰かが内側のロックに到達する前に、両方とも最初のロックを取得する時間があります。Sleep() 呼び出しがなければ、スレッドの 1 つが、他のスレッドが開始される前に、両方のロックを取得して解放する時間を持つ可能性が高くなります。

于 2009-05-21T17:22:10.333 に答える
5

誰がアクセスできるかを制御できないものを決してロックしてはならないという考えです。

タイプ オブジェクトは、すべての .net コードから見えるシングルトンであり、外部から「この」オブジェクトをロックするユーザーを制御することはできません。

同じことが文字列にも当てはまります。文字列は不変であるため、フレームワークは「ハードコードされた」文字列のインスタンスを 1 つだけ保持し、それらをプールに入れます (文字列はインターンされていると言われます)。こんにちは」、あなたはいつも同じ目的を得るでしょう。

次の例を考えてみましょう: スーパー プライベート呼び出しで Thread1 だけを書きましたが、Thread2 はバックグラウンド スレッドで使用しているライブラリによって呼び出されます...

void Thread1()
{
  lock (typeof(int))
  {
    Thread.Sleep(1000);
    lock (typeof(long))
      // do something
  }
}

void Thread2()
{
  lock (typeof(long))
  {
    Thread.Sleep(1000);
    lock (typeof(int))
      // do something
  }
}
于 2009-05-21T17:36:59.790 に答える
3

はい、どうぞ。

デッドロックの一般的な例は、複数のロックを取得し、2 つ以上のスレッドが相互に待機する場合です。

たとえば、次のようにロックする 2 つのスレッド:

Thread 1               Thread 2
 Lock "A"               Lock "B"
 Lock "B"               Lock "A" <-- both threads will stop dead here
                                     waiting for the lock to be come
                                     available.

ただし、この例ではそれを気にせず、1 つのスレッドを無期限にロックしただけです。ロックの制御を失いたくないので、これは不自然な例ですが、バックグラウンド スレッドがこのようにメイン スレッドを完全にブロックできるという事実は良くありません。

using System;
using System.Threading;

namespace ConsoleApplication7
{
    public class Program
    {
        public static void Main(string[] args)
        {
            LockableClass lockable = new LockableClass();
            new Thread(new ParameterizedThreadStart(BackgroundMethod)).Start(lockable);
            Thread.Sleep(500);
            Console.Out.WriteLine("calling Reset");
            lockable.Reset();
        }

        private static void BackgroundMethod(Object lockable)
        {
            lock (lockable)
            {
                Console.Out.WriteLine("background thread got lock now");
                Thread.Sleep(Timeout.Infinite);
            }
        }
    }

    public class LockableClass
    {
        public Int32 Value1 { get; set; }
        public Int32 Value2 { get; set; }

        public void Reset()
        {
            Console.Out.WriteLine("attempting to lock on object");
            lock (this)
            {
                Console.Out.WriteLine("main thread got lock now");
                Value1 = 0;
                Value2 = 0;
            }
        }
    }

}
于 2009-05-21T17:21:21.723 に答える
2

これはかなり標準的な悪さです。ロックを順番どおりにつかみ、ロックを付けたまま寝ます。やってはいけないことが2つ。:)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace DeadLock
{
    public class Program
    {
        static void Main(string[] args)
        {
            var ddt = new DontDoThat();

            ddt.Go();
        }
    }

    public class DontDoThat
    {
        private int _badSharedState = 0;
        private readonly object _lock1 = new object();
        private readonly object _lock2 = new object();

        public void Go()
        {
            new Thread(BadGuy1).Start();
            new Thread(BadGuy2).Start();

            Console.WriteLine("Leaving Go!");
        }

        public void BadGuy1()
        {
            lock (_lock1)
            {
                Thread.Sleep(100); // yeild with the lock is bad
                lock (_lock2)
                {
                    _badSharedState++;
                    Console.Write("From Bad Guy #1: {0})", _badSharedState );
                }
            }
        }
        public void BadGuy2()
        {
            lock (_lock2)
            {
                lock (_lock1)
                {
                    _badSharedState++;
                    Console.Write("From Bad Guy #2: {0})", _badSharedState);
                }
            }
        }
    }
}
于 2009-05-21T17:30:24.030 に答える
0
class Character
{
    public Character Other;
    public string Name;
    private object locker = new object();

    public Character(string name)
    {
        Name = name;
    }

    public void Go()
    {
        lock (locker)
        {
            Thread.Sleep(1000);
            Console.WriteLine("go in {0}", Name);
            Other.Go();
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Character a = new Character("A");
        Character b = new Character("B");
        a.Other = b;
        b.Other = a;

        new Thread(a.Go).Start();
        b.Go();

        Console.ReadLine();
    }
}
于 2011-04-06T08:43:50.673 に答える
0

問題は、lock("a string") がシングルトンをロックしていることです。これは、同じロックを使用する他のオブジェクトが無限に待機する可能性があることを意味します。

例えば:

using System;
using System.Threading;

namespace ThreadLock
{
    class Program
    {
        static void Main(string[] args)
        {
            lock ("my lock")
            {
                ManualResetEvent evt = new ManualResetEvent(false);
                WorkerObject worker = new WorkerObject(evt);
                Thread t = new Thread(new ThreadStart(worker.Work));
                t.Start();
                evt.WaitOne();
            }
        }
    }

    class WorkerObject
    {
        private ManualResetEvent _evt;
        public WorkerObject(ManualResetEvent evt)
        {
            _evt = evt;
        }
        public void Work()
        {
            lock ("my lock")
            {
                Console.WriteLine("worked.");
                _evt.Set();
            }
        }
    }
}

この場合、呼び出し元のコードは文字列にロックを作成し、ワーカー オブジェクトを作成します。Work() のワーカー オブジェクトは、C# のシングルトンである同じ文字列をロックします。呼び出し元がロックを所有しており、決して来ないシグナルを待っているため、デッドロックに陥ります。

于 2009-05-21T17:22:51.767 に答える