单例的死锁错误

本文关键字:错误 死锁 单例 | 更新日期: 2023-09-27 18:28:17

我正在努力深入设计模式,我正在构建一个快速测试来帮助我进一步理解单例模式。然而,我在.net中遇到了一个让我困惑的错误,更奇怪的是,我无法复制这个错误,这只会增加我的困惑。下面的代码不是世界上最好的代码,但我随机测试了一些东西,以帮助我更深入地理解。

class Program
{
    static void Main(string[] args)
    {
        Thread t1 = new Thread(new ThreadStart(run1));
        Thread t2 = new Thread(new ThreadStart(run2));
        t1.Priority = ThreadPriority.Lowest;
        t1.Start();
        t2.Priority = ThreadPriority.Lowest;
        t2.Start();
        Console.ReadLine();
    }
    public static void run1()
    {
        Console.WriteLine("im in run1 'n");
        TestSingleton._instance.PopulateCrudItemsProcess();
        Thread.Sleep(1000);
        Console.WriteLine(TestSingleton._instance.GetStrValue("first", 1000));
    }
    public static void run2()
    {
        Console.WriteLine("im in run2 'n");
        TestSingleton._instance.PopulateCrudItemsProcess();
        Console.WriteLine(TestSingleton._instance.GetStrValue("second", 500));
    }
}
sealed class TestSingleton
{
    private TestSingleton() { }
    public static readonly TestSingleton _instance = new TestSingleton();
    public string GetStrValue(string str, int time)
    {
        return str;
    }
    public void PopulateCrudItemsProcess()
    {
        const string proc = "[testdb].[dbo].[tstsproc]";
        string _reportingConnStr = ConfigurationManager.ConnectionStrings["reporting"].ConnectionString;
        using (SqlConnection conn = new SqlConnection(_reportingConnStr))
        {
            using (SqlCommand cmd = new SqlCommand(proc, conn))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.CommandTimeout = 7200;
                conn.Open();
                cmd.ExecuteNonQuery();
            }
        }
    }
}

现在,程序在cmd上崩溃了。ExecuteNonQuery();并表示在资源问题上出现了僵局。我希望我能捕捉到实际的错误,但正如我所说,我只能得到一次错误。根据MS的说法,这段代码是创建单例的线程安全方式。那么,你有没有想过是什么导致了这个错误呢?如有任何信息,我们将不胜感激。

单例的死锁错误

抱歉,Singleton不是线程安全的。它只包含一个对象的一个实例。

因此PopulateCrudItemsProcessGetStrValue不是线程安全的。。。

您可以尝试使用lock关键字
它允许开发人员确定必须在应用程序线程之间同步的代码范围。这是为了确保传入线程在电流结束之前不会中断电流。

举个例子:

在您的单例中声明全局

private object _oLock;

以这种方式重写PopulateCrudItemsProcess

    lock(_oLock)
    {
        using (SqlConnection conn = new SqlConnection(_reportingConnStr)) 
        { 
            using (SqlCommand cmd = new SqlCommand(proc, conn)) 
            { 
                cmd.CommandType = CommandType.StoredProcedure; 
                cmd.CommandTimeout = 7200; 
                conn.Open(); 
                cmd.ExecuteNonQuery(); 
            } 
        } 
   }

在您的情况下,关于线程安全,您必须在以下两者之间进行区分:

  • 创建Singleton
  • 在singleton实例上执行方法
  • 执行存储过程

第一部分是隐蔽的,静态构造函数在多个线程中只调用一次,因此静态变量的所有初始化代码也只执行一次。不过,这是隐含的,为了使事情易于理解,您应该使用更明确的方法,也可以在更常见的get实例方法中进行初始化,请参见下文。然而,为了让你的代码更容易为他人阅读,它在这方面已经发挥了作用。

第二部分本身也是线程安全的,因为您没有像JonSkeet已经指出的那样共享任何资源。

然而,第三部分似乎是其他人已经指出的问题。同时执行StoredProcedures或任何SQL都可能导致DB级别的死锁。在你的情况下,很可能是这样,这就是你所看到的错误。

从KarlLynch窃取存根并使实例创建显式线程安全:

public class MySingleton
{
  private static MySingleton Instance{ get; set; }
  private static readonly object initLock;
  // Private constructor
  private MySingleton()
  {
    initLock = new object();
  }
  public static MySingleton GetInstance()
  {    
    if (Instance == null)
    {
      lock(initLock)
      {
        if (Instance == null)
        {
          Instance = new MySingleton();
        }
      }
    }    
    return Instance;
  }
}