【问题标题】:Deadlock error with a singleton单例死锁错误
【发布时间】:2012-03-17 08:42:01
【问题描述】:

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

【问题讨论】:

标签: c# .net design-patterns singleton


【解决方案1】:

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

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

【讨论】:

  • 嗯,但他们是。此代码未生成异常。
  • 就单例而言,它们线程安全的。类中没有共享状态 - 哎呀,方法可以设为静态。您认为它们在哪里确切地不是线程安全的? (我希望存储过程可能会由于尝试同时在两个线程中填充数据库而失败,但这与这些方法本身不是线程安全的不同。如果您尝试运行,您会得到相同的结果两个单独的进程同时使用相同的方法。)当然,它们不是线程安全的因为它是单例,但那是另一回事。
【解决方案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(); 
            } 
        } 
   }

【讨论】:

  • 在看到来自@Skeet 和@Hans 的cmets 之后,我不确定您的问题是否被阻止。您能否按照@Nikola Markovinović 的说法显示您的"[testdb].[dbo].[tstsproc]"; 的代码
【解决方案3】:

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

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

您得到的第一部分是隐蔽的,静态构造函数只在多个线程中调用一次,因此静态变量的所有初始化代码也只执行一次。尽管这是隐含的,并且本着使事情易于理解的精神,您应该使用更明确的方法,也在更常见的 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;
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-06
    • 1970-01-01
    • 2012-01-11
    • 2015-09-20
    • 2021-10-08
    • 1970-01-01
    • 2012-10-30
    • 1970-01-01
    相关资源
    最近更新 更多