C#静态构造函数线程是否安全?

时间:2023-01-07 11:46:49

In other words, is this Singleton implementation thread safe:

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

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

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

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

10 个解决方案

#1


172  

Static constructors are guaranteed to be run only once per application domain, before any instances of a class are created or any static members are accessed. http://msdn.microsoft.com/en-us/library/aa645612.aspx

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

The implementation shown is thread safe for the initial construction, that is, no locking or null testing is required for constructing the Singleton object. However, this does not mean that any use of the instance will be synchronised. There are a variety of ways that this can be done; I've shown one below.

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

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();
    }
}

#2


77  

While all of these answers are giving the same general answer, there is one caveat.

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

Remember that all potential derivations of a generic class are compiled as individual types. So use caution when implementing static constructors for generic types.

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

class MyObject<T>
{
    static MyObject() 
    {
       //this code will get executed for each T.
    }
}

EDIT:

Here is the demonstration:

这是演示:

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()));        
    }
}

In the console:

在控制台中:

Hit System.Object
Hit System.String

#3


24  

Using a static constructor actually is threadsafe. The static constructor is guaranteed to be executed only once.

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

From the C# language specification http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx:

来自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.
  • 引用该类的任何静态成员。

So yes, you can trust that your singleton will be correctly instantiated.

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

Zooba made an excellent point (and 15 seconds before me, too!) that the static constructor will not guarantee thread-safe shared access to the singleton. That will need to be handled in another manner.

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

#4


7  

Here's the Cliffnotes version from the above MSDN page on c# singleton:

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

Use the following pattern, always, you can't go wrong:

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

public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();

   private Singleton(){}

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

Beyond the obvious singleton features, it gives you these two things for free (in respect to singleton in c++):

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

  1. lazy construction (or no construction if it was never called)
  2. 懒惰的结构(如果从未调用过,则不构造)

  3. synchronization

#5


4  

Static constructors are guaranteed to fire only once per App Domain so your approach should be OK. However, it is functionally no different from the more concise, inline version:

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

private static readonly Singleton instance = new Singleton();

Thread safety is more of an issue when you are lazily initializing things.

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

#6


3  

The Common Language Infrastructure specification guarantees that "a type initializer shall run exactly once for any given type, unless explicitly called by user code." (Section 9.5.3.1.) So unless you have some whacky IL on the loose calling Singleton::.cctor directly (unlikely) your static constructor will run exactly once before the Singleton type is used, only one instance of Singleton will be created, and your Instance property is thread-safe.

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

Note that if Singleton's constructor accesses the Instance property (even indirectly) then the Instance property will be null. The best you can do is detect when this happens and throw an exception, by checking that instance is non-null in the property accessor. After your static constructor completes the Instance property will be non-null.

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

As Zoomba's answer points out you will need to make Singleton safe to access from multiple threads, or implement a locking mechanism around using the singleton instance.

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

#7


2  

Just to be pedantic, but there is no such thing as a static constructor, but rather static type initializers, here's a small demo of cyclic static constructor dependency which illustrates this point.

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

#8


1  

Static constructor is guaranteed to be thread safe. Also, check out the discussion on Singleton at DeveloperZen: http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/

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

#9


1  

The static constructor will finish running before any thread is allowed to access the class.

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

    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();
    }

The code above produced the results below.

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

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).

Even though the static constructor took a long time to run, the other threads stopped and waited. All threads read the value of _x set at the bottom of the static constructor.

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

#10


0  

Although other answers are mostly correct, there is yet another caveat with static constructors.

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

As per section II.10.5.3.3 Races and deadlocks of the ECMA-335 Common Language Infrastructure

根据第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.

除非从类型初始化程序(直接或间接)调用的某些代码明确调用阻塞操作,否则单独的类型初始化不应创建死锁。

The following code results in a deadlock

以下代码导致死锁

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();
    }
}

Original author is Igor Ostrovsky, see his post here.

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

#1


172  

Static constructors are guaranteed to be run only once per application domain, before any instances of a class are created or any static members are accessed. http://msdn.microsoft.com/en-us/library/aa645612.aspx

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

The implementation shown is thread safe for the initial construction, that is, no locking or null testing is required for constructing the Singleton object. However, this does not mean that any use of the instance will be synchronised. There are a variety of ways that this can be done; I've shown one below.

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

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();
    }
}

#2


77  

While all of these answers are giving the same general answer, there is one caveat.

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

Remember that all potential derivations of a generic class are compiled as individual types. So use caution when implementing static constructors for generic types.

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

class MyObject<T>
{
    static MyObject() 
    {
       //this code will get executed for each T.
    }
}

EDIT:

Here is the demonstration:

这是演示:

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()));        
    }
}

In the console:

在控制台中:

Hit System.Object
Hit System.String

#3


24  

Using a static constructor actually is threadsafe. The static constructor is guaranteed to be executed only once.

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

From the C# language specification http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx:

来自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.
  • 引用该类的任何静态成员。

So yes, you can trust that your singleton will be correctly instantiated.

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

Zooba made an excellent point (and 15 seconds before me, too!) that the static constructor will not guarantee thread-safe shared access to the singleton. That will need to be handled in another manner.

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

#4


7  

Here's the Cliffnotes version from the above MSDN page on c# singleton:

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

Use the following pattern, always, you can't go wrong:

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

public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();

   private Singleton(){}

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

Beyond the obvious singleton features, it gives you these two things for free (in respect to singleton in c++):

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

  1. lazy construction (or no construction if it was never called)
  2. 懒惰的结构(如果从未调用过,则不构造)

  3. synchronization

#5


4  

Static constructors are guaranteed to fire only once per App Domain so your approach should be OK. However, it is functionally no different from the more concise, inline version:

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

private static readonly Singleton instance = new Singleton();

Thread safety is more of an issue when you are lazily initializing things.

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

#6


3  

The Common Language Infrastructure specification guarantees that "a type initializer shall run exactly once for any given type, unless explicitly called by user code." (Section 9.5.3.1.) So unless you have some whacky IL on the loose calling Singleton::.cctor directly (unlikely) your static constructor will run exactly once before the Singleton type is used, only one instance of Singleton will be created, and your Instance property is thread-safe.

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

Note that if Singleton's constructor accesses the Instance property (even indirectly) then the Instance property will be null. The best you can do is detect when this happens and throw an exception, by checking that instance is non-null in the property accessor. After your static constructor completes the Instance property will be non-null.

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

As Zoomba's answer points out you will need to make Singleton safe to access from multiple threads, or implement a locking mechanism around using the singleton instance.

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

#7


2  

Just to be pedantic, but there is no such thing as a static constructor, but rather static type initializers, here's a small demo of cyclic static constructor dependency which illustrates this point.

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

#8


1  

Static constructor is guaranteed to be thread safe. Also, check out the discussion on Singleton at DeveloperZen: http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/

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

#9


1  

The static constructor will finish running before any thread is allowed to access the class.

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

    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();
    }

The code above produced the results below.

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

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).

Even though the static constructor took a long time to run, the other threads stopped and waited. All threads read the value of _x set at the bottom of the static constructor.

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

#10


0  

Although other answers are mostly correct, there is yet another caveat with static constructors.

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

As per section II.10.5.3.3 Races and deadlocks of the ECMA-335 Common Language Infrastructure

根据第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.

除非从类型初始化程序(直接或间接)调用的某些代码明确调用阻塞操作,否则单独的类型初始化不应创建死锁。

The following code results in a deadlock

以下代码导致死锁

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();
    }
}

Original author is Igor Ostrovsky, see his post here.

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