单例死锁错误

时间:2012-03-17 08:42:02

标签: c# .net design-patterns singleton

我正在尝试更多地设计模式,我正在构建一个快速测试来帮助我进一步理解单例模式。然而,我在.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,这段代码是一种创建单例的线程安全方法。那么对可能导致错误的原因有什么看法?任何信息都非常感谢。

3 个答案:

答案 0 :(得分:5)

很抱歉,但是Singleton不是关于线程安全的。它只有一个对象的实例。

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

答案 1 :(得分:2)

您可以尝试使用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(); 
            } 
        } 
   }

答案 2 :(得分:1)

关于您的情况下的线程安全性,您必须介于:

之间
  • 创建单身人士
  • 在单例实例上执行方法
  • 执行存储过程

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

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

第三部分似乎是其他人已经指出的问题。同时执行StoredProcedures或任何SQL会导致数据库级别的死锁。在你的情况下很可能它会发生,这是你看到的错误。

从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;
  }
}