lock(this)
、lock(typeof(MyType))
、lock("a string")
は、別のスレッドが同じキーをロックして、デッドロック。この問題を理解するために、デッドロックを説明するためのサンプルコードを作成しようとしましたが、これに頭を悩ませることができませんでした。
誰かがこの古典的な問題を説明する簡潔なコードを書くことができますか?短くしてください。コードは小さなチャンクにしかダイジェストできません。
編集: lassevkはそれをうまくまとめていると思います。本当の問題は、ロックの制御を失ったことです。これが発生すると、ロックが呼び出される順序を制御できなくなり、潜在的なデッドロック状態が発生する可能性があります。
lock(this)
、lock(typeof(MyType))
などはすべて、制御できないロックを選択した状況です。
デッドロックは、複数のロックがある場合にのみ発生します。両方のスレッドが他方が必要とするリソースを保持している状況が必要です(つまり、少なくとも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つは、他のスレッドが開始される前に、両方のロックを取得して解放する時間がほとんどあります。
アイデアは、誰がアクセスできるかを制御できないものを決してロックしてはならないということです。
タイプオブジェクトは、すべての.netコードに表示されるシングルトンであり、「this」オブジェクトを外部からロックするユーザーを制御することはできません。
文字列についても同じことが言えます。文字列は不変であるため、コードに文字列を2回書き込むと、フレームワークは「ハードコードされた」文字列のインスタンスを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
}
}
確かに、ここに行きます。
デッドロックの一般的な例は、複数のロックを取得し、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;
}
}
}
}
これはかなり標準的な悪です。ロックを順不同でつかみ、ロックを持って寝ます。やるべき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);
}
}
}
}
}
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();
}
}