关于多线程:C#静态构造函数线程是否安全?

关于多线程:C#静态构造函数线程是否安全?

Is the C# static constructor thread safe?

换句话说,这个Singleton实现线程是否安全:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

    static Singleton()
    {
        instance = new Singleton();
    }

    public static Singleton Instance
    {
        get { return instance; }
    }
}

在创建类的任何实例或访问任何静态成员之前,保证每个应用程序域只运行一次静态构造函数。 http://msdn.microsoft.com/en-us/library/aa645612.aspx

显示的实现对于初始构造是线程安全的,也就是说,构造Singleton对象不需要锁定或空测试。但是,这并不意味着将同步实例的任何使用。有多种方法可以做到这一点;我在下面展示了一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Singleton
{
    private static Singleton instance;
    // Added a static mutex for synchronising use of instance.
    private static System.Threading.Mutex mutex;
    private Singleton() { }
    static Singleton()
    {
        instance = new Singleton();
        mutex = new System.Threading.Mutex();
    }

    public static Singleton Acquire()
    {
        mutex.WaitOne();
        return instance;
    }

    // Each call to Acquire() requires a call to Release()
    public static void Release()
    {
        mutex.ReleaseMutex();
    }
}

虽然所有这些答案都给出了相同的一般答案,但有一点需要注意。

请记住,泛型类的所有潜在派生都是作为单独的类型编译的。因此在为泛型类型实现静态构造函数时要小心。

1
2
3
4
5
6
7
class MyObject< T >
{
    static MyObject()
    {
       //this code will get executed for each T.
    }
}

编辑:

这是演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
static void Main(string[] args)
{
    var obj = new Foo<object>();
    var obj2 = new Foo<string>();
}

public class Foo< T >
{
    static Foo()
    {
         System.Diagnostics.Debug.WriteLine(String.Format("Hit {0}", typeof(T).ToString()));        
    }
}

在控制台中:

1
2
Hit System.Object
Hit System.String


使用静态构造函数实际上是线程安全的。静态构造函数保证只执行一次。

来自C#语言规范http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx:

The static constructor for a class executes at most once in a given application domain. The execution of a static constructor is triggered by the first of the following events to occur within an application domain:

  • An instance of the class is created.
  • Any of the static members of the class are referenced.

所以,是的,您可以相信您的单例将被正确实例化。

Zooba提出了一个很好的观点(在我之前15秒!)静态构造函数不保证对单例的线程安全共享访问。这将需要以另一种方式处理。


这是c#singleton上面的MSDN页面中的Cliffnotes版本:

使用以下模式,总是,你不能出错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();

   private Singleton(){}

   public static Singleton Instance
   {
      get
      {
         return instance;
      }
   }
}

除了明显的单例功能之外,它还免费提供这两件事(关于c ++中的单例):

  • 懒惰的结构(如果从未调用过,则不构造)
  • 同步

  • 静态构造函数保证每个App Domain只触发一次,因此您的方法应该没问题。但是,它在功能上与更简洁的内联版本没有区别:

    1
    private static readonly Singleton instance = new Singleton();

    当您懒洋洋地初始化事物时,线程安全性更成问题。


    在允许任何线程访问该类之前,静态构造函数将完成运行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
        private class InitializerTest
        {
            static private int _x;
            static public string Status()
            {
                return"_x =" + _x;
            }
            static InitializerTest()
            {
                System.Diagnostics.Debug.WriteLine("InitializerTest() starting.");
                _x = 1;
                Thread.Sleep(3000);
                _x = 2;
                System.Diagnostics.Debug.WriteLine("InitializerTest() finished.");
            }
        }

        private void ClassInitializerInThread()
        {
            System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() +": ClassInitializerInThread() starting.");
            string status = InitializerTest.Status();
            System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() +": ClassInitializerInThread() status =" + status);
        }

        private void classInitializerButton_Click(object sender, EventArgs e)
        {
            new Thread(ClassInitializerInThread).Start();
            new Thread(ClassInitializerInThread).Start();
            new Thread(ClassInitializerInThread).Start();
        }

    上面的代码产生了以下结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    10: ClassInitializerInThread() starting.
    11: ClassInitializerInThread() starting.
    12: ClassInitializerInThread() starting.
    InitializerTest() starting.
    InitializerTest() finished.
    11: ClassInitializerInThread() status = _x = 2
    The thread 0x2650 has exited with code 0 (0x0).
    10: ClassInitializerInThread() status = _x = 2
    The thread 0x1f50 has exited with code 0 (0x0).
    12: ClassInitializerInThread() status = _x = 2
    The thread 0x73c has exited with code 0 (0x0).

    即使静态构造函数需要很长时间才能运行,其他线程也会停止并等待。 所有线程都读取静态构造函数底部的_x值。


    公共语言基础结构规范保证"除非用户代码明确调用,否则类型初始值设定项应对任何给定类型运行一次。" (第9.5.3.1节。)因此,除非你在松散调用Singleton ::。cctor上有一些令人讨厌的IL(不太可能),你的静态构造函数将在使用Singleton类型之前运行一次,只会创建一个Singleton实例,并且您的Instance属性是线程安全的。

    请注意,如果Singleton的构造函数访问Instance属性(甚至是间接的),那么Instance属性将为null。您可以做的最好的事情是通过检查属性访问器中的实例是否为null来检测何时发生这种情况并抛出异常。静态构造函数完成后,Instance属性将为非null。

    正如Zoomba的回答所指出的那样,你需要让Singleton安全地从多个线程访问,或者使用单例实例实现锁定机制。


    只是为了迂腐,但没有静态构造函数,而是静态类型初始化器,这里是一个循环静态构造函数依赖的小演示,它说明了这一点。


    静态构造函数保证是线程安全的。
    另外,请查看DeveloperZen上关于Singleton的讨论:
    http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/


    虽然其他答案大多是正确的,但还有一个静态构造函数的警告。

    根据第II.10.5.3.3节ECMA-335共同语言的种族和僵局
    基础设施

    Type initialization alone shall not create a deadlock unless some code
    called from a type initializer (directly or indirectly) explicitly
    invokes blocking operations.

    以下代码导致死锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    using System.Threading;
    class MyClass
    {
        static void Main() { /* Won’t run... the static constructor deadlocks */  }

        static MyClass()
        {
            Thread thread = new Thread(arg => { });
            thread.Start();
            thread.Join();
        }
    }

    原作者是伊戈尔奥斯特罗夫斯基,请看他的帖子。


    推荐阅读