Thread Safe C # Singleton Pattern

Tengo algunas preguntas sobre el patrón singleton como se documenta aquí: http://msdn.microsoft.com/en-us/library/ff650316.aspx

El siguiente código es un extracto del artículo:

using System; public sealed class Singleton { private static volatile Singleton instance; private static object syncRoot = new object(); private Singleton() {} public static Singleton Instance { get { if (instance == null) { lock (syncRoot) { if (instance == null) instance = new Singleton(); } } return instance; } } } 

Específicamente, en el ejemplo anterior, ¿existe la necesidad de comparar instancia a nulo dos veces, antes y después del locking? ¿Es esto necesario? ¿Por qué no realizar primero el locking y hacer la comparación?

¿Hay algún problema al simplificar lo siguiente?

  public static Singleton Instance { get { lock (syncRoot) { if (instance == null) instance = new Singleton(); } return instance; } } 

¿Es caro el locking?

¡Realizar el locking es terriblemente caro si se compara con la instance != null verificación de puntero simple instance != null .

El patrón que ve aquí se llama locking con doble verificación . Su objective es evitar la costosa operación de locking que solo se necesitará una vez (cuando se accede por primera vez al singleton). La implementación es tal porque también debe garantizar que cuando se inicialice el singleton no haya errores que resulten de las condiciones de carrera de subprocesos.

Piénselo de esta manera: se garantiza que un cheque null (sin lock ) le dará una respuesta utilizable correcta solo cuando esa respuesta sea “sí, el objeto ya está construido”. Pero si la respuesta es “aún no está construida”, entonces no tienes suficiente información porque lo que realmente querías saber es que “no está construida todavía y ningún otro hilo tiene la intención de construirla en breve “. Entonces usa el control externo como una prueba inicial muy rápida e inicia el procedimiento correcto, libre de errores pero “costoso” (locking y verificación) solo si la respuesta es “no”.

La implementación anterior es lo suficientemente buena para la mayoría de los casos, pero en este punto es una buena idea ir a leer el artículo de Jon Skeet sobre singletons en C #, que también evalúa otras alternativas.

La versión perezosa:

 public sealed class Singleton { static readonly Lazy lazy = new Lazy(() => new Singleton()); private Singleton() { } public static Singleton Instance => lazy.Value; } 

Requiere .NET 4 y C # 6.0 (VS2015) o más reciente.

Realizar un locking: bastante barato (aún más caro que una prueba nula).

Realización de un locking cuando otro hilo lo tiene: Usted obtiene el costo de lo que aún tienen que hacer mientras lo bloquea, agregado a su propio tiempo.

Realizar un locking cuando otro hilo lo tiene, y docenas de otros subprocesos también lo están esperando: Aterrador.

Por motivos de rendimiento, siempre quiere tener lockings que quiera otro subproceso, durante el menor tiempo posible.

Por supuesto, es más fácil razonar sobre cerraduras “anchas” que estrechas, por lo que vale la pena comenzar con ellas de manera amplia y optimizarlas según sea necesario, pero hay casos en los que aprendemos de la experiencia y la familiaridad donde un patrón más estrecho se ajusta al patrón.

(Por cierto, si es posible que simplemente utilices private static volatile Singleton instance = new Singleton() o si simplemente no puedes usar singletons pero utilizas una clase estática, ambos son mejores en lo que respecta a estas preocupaciones).

El motivo es el rendimiento. Si instance != null (que siempre será el caso excepto la primera vez), no hay necesidad de hacer un lock costoso: dos hilos que acceden al singleton inicializado simultáneamente se sincronizarían innecesariamente.

En casi todos los casos (es decir, todos los casos, excepto los primeros), la instance no será nula. La adquisición de un locking es más costosa que una simple comprobación, por lo que la comprobación una vez que el valor de la instance antes del locking sea una optimización agradable y gratuita.

Este patrón se llama locking con doble verificación: http://en.wikipedia.org/wiki/Double-checked_locking

Jeffrey Richter recomienda lo siguiente:

 public sealed class Singleton { private static readonly Object s_lock = new Object(); private static Singleton instance = null; private Singleton() { } public static Singleton Instance { get { if(instance != null) return instance; Monitor.Enter(s_lock); Singleton temp = new Singleton(); Interlocked.Exchange(ref instance, temp); Monitor.Exit(s_lock); return instance; } } } 

Podría crear ansiosamente la instancia Singleton segura para subprocesos, dependiendo de las necesidades de su aplicación, este es un código sucinto, aunque preferiría la versión perezosa de @andasa.

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