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

uri*_*ini 242 c# singleton multithreading

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

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

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

    public static Singleton Instance
    {
        get { return instance; }
    }
}
Run Code Online (Sandbox Code Playgroud)

Zoo*_*oba 185

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

显示的实现对于初始构造是线程安全的,也就是说,构造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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果您的单例对象是不可变的,则使用互斥锁或任何同步机制是一种过度杀伤,不应使用.另外,我发现上面的示例实现非常脆弱:-).使用Singleton.Acquire()的所有代码都需要在使用单例实例完成时调用Singleton.Release().没有这样做(例如,过早返回,通过异常保留范围,忘记调用Release),下次从不同的线程访问此Singleton时,它将在Singleton.Acquire()中死锁. (52认同)
  • 减少Release()方法脆弱性的一种方法是使用另一个具有IDisposable的类作为同步处理程序.当您获得单例时,您将获得处理程序并可以将需要单例的代码放入使用块来处理释放. (26认同)
  • 这些天的答案是使用`Lazy <T>` - 任何使用我最初发布的代码的人做错了(老实说,开始时并不是那么好 - 5年前 - 我不是擅长这个东西,因为当前我是:)). (11认同)
  • 对于可能因此而被绊倒的其他人:初始化具有初始化器的任何静态字段成员_before_调用静态构造函数. (5认同)
  • 同意,虽然我会走得更远.如果你的单身人士是不可改变的,那么使用单身人士是过度的.只需定义常量.最终,正确使用单身人士需要开发人员知道他们在做什么.与此实现一样脆弱,它仍然优于问题中那些错误随机显现而不是明显未发布的互斥锁. (2认同)

Bri*_*lph 85

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

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

class MyObject<T>
{
    static MyObject() 
    {
       //this code will get executed for each T.
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:

这是演示:

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()));        
    }
}
Run Code Online (Sandbox Code Playgroud)

在控制台中:

Hit System.Object
Hit System.String
Run Code Online (Sandbox Code Playgroud)

  • 我认为这是我想要做的.通用类型根据使用的泛型参数编译为单独的类型,因此可以并且将多次调用静态构造函数. (6认同)
  • @sll:不正确...请参阅我的编辑 (2认同)
  • 有趣但非常静态的cosntructor调用了所有类型,只是尝试了多种引用类型 (2认同)

Der*_*ark 28

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

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

类的静态构造函数在给定的应用程序域中最多执行一次.静态构造函数的执行由应用程序域中发生的以下第一个事件触发:

  • 创建了一个类的实例.
  • 引用该类的任何静态成员.

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

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


小智 8

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

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

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

   private Singleton(){}

   public static Singleton Instance
   {
      get 
      {
         return instance; 
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 如果班级没有任何其他不相关的静态(如consts),则会很懒惰.否则,访问任何静态方法或属性将导致实例创建.所以我不会称之为懒惰. (3认同)

And*_*ers 6

保证静态构造函数在每个App Domain中仅触发一次,因此您的方法应该可以。但是,它在功能上与更简洁的嵌入式版本没有什么不同:

private static readonly Singleton instance = new Singleton();
Run Code Online (Sandbox Code Playgroud)

延迟初始化事物时,线程安全性更成问题。

  • 安德鲁,那并不完全等同。通过不使用静态构造函数,有关何时执行初始化程序的某些保证将丢失。请查看以下链接以获取详细说明:* &lt;http://csharpindepth.com/Articles/General/Beforefieldinit.aspx&gt; * &lt;http://www.ondotnet.com/pub/a/dotnet/2003/07/ 07 / staticxtor.html&gt; (4认同)

Tra*_*lip 5

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

    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();
    }
Run Code Online (Sandbox Code Playgroud)

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

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).
Run Code Online (Sandbox Code Playgroud)

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