31

我读过几篇文章和帖子说lock(this),都是不好的做法,因为另一个线程可能会锁定同一个键并导致死锁lock(typeof(MyType))lock("a string")为了理解这个问题,我试图创建一些示例代码来说明死锁,但一直无法解决这个问题。

有人可以写一段简明的代码来说明这个经典问题吗?请保持简短,我只能以较小的块消化代码。

编辑: 我认为 lassevk 总结得很好;真正的问题是你失去了对锁的控制。一旦发生这种情况,您将无法控制调用锁的顺序,并且您正在允许潜在的死锁情况。

lock(this), lock(typeof(MyType)), 等等都是您选择了无法控制的锁的情况。

4

6 回答 6

34

只有当您拥有多个锁时才会发生死锁。您需要这样一种情况,即两个线程都拥有另一个需要的资源(这意味着必须至少有两个资源,并且两个线程必须尝试以不同的顺序获取它们)

一个简单的例子:

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

假设两个线程都在一秒钟内启动,那么它们都有时间在任何人到达内部锁之前抢到第一个锁。如果没有 Sleep() 调用,其中一个线程很可能在另一个线程启动之前就有时间获取和释放两个锁。

于 2009-05-21T17:22:10.333 回答
5

这个想法是,你永远不应该锁定你无法控制谁可以访问的东西。

类型对象是对每个 .net 代码段可见的单例,您无法从外部控制谁锁定您的“this”对象。

字符串也是如此:由于字符串是不可变的,因此框架只保留一个“硬编码”字符串的实例并将它们放入一个池中(该字符串被称为被实习),如果您在代码中写入两次字符串“你好”,你总是会得到同样的下贱。

考虑以下示例:您在超级私有调用中只编写了 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

喏,给你。

请注意,死锁的常见示例是当您获得多个锁时,两个或多个线程最终相互等待。

例如,两个像这样锁定的线程:

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.

然而,在这个例子中我并没有为此烦恼,我只是让一个线程无限期地锁定。你真的不想放松对你的锁的控制,所以虽然这是一个人为的例子,但后台线程可以像这样完全阻塞主线程的事实是很糟糕的。

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

这是相当标准的坏事。不按顺序抓住锁,然后用锁睡觉。做两件坏事。:)

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

问题是 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 回答
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 回答