2008-08-10 22 views
213

En otras palabras, es esta Singleton segura hilo aplicación:¿Es seguro el hilo del constructor estático de C#?

public class Singleton 
{ 
    private static Singleton instance; 

    private Singleton() { } 

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

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

Es thread-safe. Supongamos que varios subprocesos quieren obtener la propiedad 'Instancia' de una vez. Se le pedirá a uno de los subprocesos que primero ejecute el inicializador de tipo (también conocido como el constructor estático). Mientras tanto, todos los otros hilos que quieran leer la propiedad 'Instance', serán *** bloqueados *** hasta que el inicializador de tipo haya terminado. Solo después de que el inicializador de campo haya concluido, se permitirá que los subprocesos obtengan el valor de 'Instancia'. Entonces nadie puede ver que 'Instance' sea' null'. –

Respuesta

162

Se garantiza que los constructores estáticos se ejecuten solo una vez por dominio de aplicación, antes de que se creen instancias de una clase o se acceda a cualquier miembro estático. http://msdn.microsoft.com/en-us/library/aa645612.aspx

La implementación que se muestra es hilo seguro para la construcción inicial, es decir, no se requiere de bloqueo o pruebas nulo para construir el objeto Singleton. Sin embargo, esto no significa que cualquier uso de la instancia se sincronizará. Hay una variedad de formas en que esto se puede hacer; He mostrado uno a continuación.

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

Tenga en cuenta que si su objeto singleton es inmutable, usar un mutex o cualquier mecanismo de sincronización es una exageración y no debe utilizarse. Además, considero que la implementación de la muestra anterior es extremadamente frágil :-). Se espera que todo el código que utiliza Singleton.Acquire() llame a Singleton.Release() cuando finalice con la instancia de singleton. Si no lo hace (por ejemplo, regresar prematuramente, dejar el alcance a través de una excepción, olvidarse de llamar a Release), la próxima vez que se acceda a este Singleton desde un hilo diferente, se bloqueará en Singleton.Acquire(). –

+2

De acuerdo, aunque iría más lejos. Si su singleton es inmutable, usar un singleton es excesivo. Solo defina constantes. En última instancia, el uso de un singleton correctamente requiere que los desarrolladores sepan lo que están haciendo. A pesar de lo frágil que es esta implementación, sigue siendo mejor que la de la pregunta en la que esos errores se manifiestan al azar en lugar de ser un mutex obviamente inédito. – Zooba

+23

Una forma de disminuir la fragilidad del método Release() es utilizar otra clase con IDisposable como controlador de sincronización. Cuando adquiere el singleton, obtiene el controlador y puede poner el código que requiere el singleton en un bloque de uso para manejar el lanzamiento. – CodexArcanum

2

Los Common Language Infrastructure specification garantiza que "un tipo de inicialización se ejecutan exactamente una vez para cualquier tipo determinado, a menos que se llama explícitamente por código de usuario." (Sección 9.5.3.1.) Así que, a menos que tengas un poco de IL alocado llamando a Singleton ::. Cctor directamente (improbable) tu constructor estático se ejecutará exactamente una vez antes de que se use el tipo Singleton, solo se creará una instancia de Singleton, y su propiedad Instance es segura para subprocesos.

Tenga en cuenta que si el constructor de Singleton accede a la propiedad Instance (incluso indirectamente), entonces la propiedad Instance será nula. Lo mejor que puede hacer es detectar cuándo sucede esto y lanzar una excepción, verificando que la instancia no sea nula en el acceso a la propiedad. Después de que su constructor estático complete la propiedad Instance no será nulo.

Como Zoomba's answer señala que necesitará hacer que Singleton sea seguro para acceder desde múltiples hilos, o implementar un mecanismo de bloqueo alrededor del uso de la instancia singleton.

22

El uso de un constructor estático realidad es multi-hilo. Se garantiza que el constructor estático se ejecute solo una vez.

De la especificación del lenguaje C# http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx:

El constructor estático para una clase ejecuta como máximo una vez en un dominio de aplicación dada. La ejecución de un constructor estático se desencadena cuando se produce el primero de los siguientes eventos dentro de un dominio de aplicación:

  • Se crea una instancia de la clase.
  • Se hace referencia a cualquiera de los miembros estáticos de la clase.

Así que sí, puede confiar en que su producto único se creará una instancia correctamente.

Zooba hizo un excelente punto (¡y 15 segundos antes que yo también!) Que el constructor estático no garantiza el acceso compartido seguro para subprocesos al singleton. Eso tendrá que ser manejado de otra manera.

3

Los constructores estáticos están garantizados para disparar solo una vez por Dominio de aplicaciones, por lo que su enfoque debería ser correcto. Sin embargo, es funcionalmente diferente de la versión más concisa, en línea:

private static readonly Singleton instance = new Singleton(); 

Seguridad de los hilos es más de un problema cuando se está inicializando cosas pereza.

+3

Andrew, eso no es totalmente equivalente. Por nosotros no En un constructor estático, se pierden algunas de las garantías sobre cuándo se ejecutará el inicializador. Consulte estos enlaces para obtener una explicación detallada: * *

+0

Derek, estoy familiarizado con * beforefieldinit * "optimización" pero, personalmente, nunca me preocupo por eso. –

+0

enlace de trabajo para el comentario de @ DerekPark: http://csharpindepth.com/Articles/General/Beforefieldinit.aspx. Este enlace parece estar obsoleto: http://www.ondotnet.com/pub/a/dotnet/2003/07/07/staticxtor.html – phoog

74

Si bien todas estas respuestas están dando la misma respuesta general, hay una advertencia.

Recuerde que todas las posibles derivaciones de una clase genérica se compilan como tipos individuales. Por lo tanto, tenga cuidado al implementar constructores estáticos para tipos genéricos.

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

EDIT:

Aquí es la demostración:

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

En la consola:

Hit System.Object 
Hit System.String 
+0

typeof (MyObject )! = Typeof (MyObject ); –

+3

Creo que ese es el punto que intento hacer. Los tipos genéricos se compilan como tipos individuales en función de qué parámetros genéricos se utilizan, por lo que el constructor estático se puede y se llamará varias veces. –

+0

Esto es correcto cuando T es del tipo de valor, para el tipo de referencia T solo se generaría un tipo genérico – sll

2

sólo para ser pedante, pero no hay tal cosa como una estática constructor, sino inicializadores de tipo estático, here's a small demo de constructor estático cíclico pendencia que ilustra este punto.

+0

Microsoft parece estar en desacuerdo. https://msdn.microsoft.com/en-us/library/k9x6w0hc.aspx – Westy92

7

Aquí está la versión Cliffnotes desde la página anterior MSDN en C# Singleton:

Utilice el siguiente patrón, siempre, no se puede ir mal:

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

    private Singleton(){} 

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

Más allá de las características simples obvias, da que estas dos cosas de forma gratuita (con respecto a Singleton en C++):

  1. construcción vago (o ninguna construcción si nunca fue llamado)
  2. sincronización
+2

Lazy si la clase no tiene ninguna otra estática no relacionada (como consts). De lo contrario, el acceso a cualquier método o propiedad estáticos dará como resultado la creación de instancias. Entonces no lo llamaría flojo. – Schultz9999

+1

@ShmilTheCat: ¿Qué tiene que ver C++ con esto? – stakx

0

Aunque otras respuestas son en su mayoría correctas, existe otra advertencia con constructores estáticos.

Como se indica en la sección II.10.5.3.3 razas y callejones sin salida de laECMA-335 Common Language Infrastructure

Tipo de inicialización por sí sola no creará un callejón sin salida a menos que algún código llamada desde un inicializador de tipo (directa o indirectamente) de forma explícita invoca operaciones de bloqueo.

el código siguiente produce en un punto muerto

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

autor original es Igor Ostrovsky, consulte a su puesto here.

1

El constructor estático finaliza ejecuta antes de cualquier subproceso tiene permiso para acceder a la clase.

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

El código de arriba produjo los resultados a continuación.

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

pesar de que el constructor estático tomó mucho tiempo para correr, los otros hilos se detuvo y esperó. Todos los subprocesos leen el valor de _x establecido en la parte inferior del constructor estático.

Cuestiones relacionadas