2010-06-04 21 views
12

He estado utilizando este patrón para inicializar datos estáticos en mis clases. Parece seguro para mí, pero sé lo sutiles que pueden ser los problemas de enhebrado. Aquí está el código:Inicialización segura de subprocesos de variables estáticas

public class MyClass // bad code, do not use 
{ 
    static string _myResource = ""; 
    static volatile bool _init = false; 
    public MyClass() 
    { 
     if (_init == true) return; 
     lock (_myResource) 
     { 
      if (_init == true) return; 
      Thread.Sleep(3000); // some operation that takes a long time 
      _myResource = "Hello World"; 
      _init = true; 
     } 
    } 
    public string MyResource { get { return _myResource; } } 
} 

¿Hay algún agujero aquí? Tal vez hay una manera más simple de hacer esto.

ACTUALIZACIÓN: El consenso parece ser que un constructor estático es el camino a seguir. Se me ocurrió la siguiente versión usando un constructor estático.

public class MyClass 
{ 
    static MyClass() // a static constructor 
    { 
     Thread.Sleep(3000); // some operation that takes a long time 
     _myResource = "Hello World"; 
    } 

    static string _myResource = null; 

    public MyClass() { LocalString = "Act locally"; } // an instance constructor 

    // use but don't modify 
    public bool MyResourceReady { get { return _myResource != null; } } 
    public string LocalString { get; set; } 
} 

Espero que esto sea mejor.

+0

@RaoulRobin, un constructor estático estaría en orden, pero solo si quiere una clase estática. Si no desea compartir sus campos en todas las instancias de MyClass, debe emplear una sincronización adecuada. – Kiril

+0

En la versión de la vida real, el recurso estático es Dictionary <> que se carga previamente, que se lee simultáneamente en varias instancias de la clase. MSDN dice que las lecturas concurrentes están bien siempre que el Diccionario no se modifique durante las lecturas. Gracias por el comentario. – RaoulRubin

Respuesta

1

Dependiendo de su caso de uso (es decir, si las discusiones no tienen que pasar información entre sí utilizando esta variable) , marcando la variable miembro como [ThreadStatic] puede ser una solución.
Ver here.

6

Realizar lock() en _myResource y cambiarlo por lock() parece una mala idea. Considere siguiente flujo de trabajo:

  1. hilo 1 llama MyClass().
  2. ejecución se detiene antes de la línea _init = true; justo después de asignar _myResource.
  3. procesador cambia a la rosca 2.
  4. rosca 2 llamadas MyClass(). Como _init sigue siendo false y la referencia _myResource ha cambiado, entra con éxito en el bloque de instrucción lock().
  5. _init sigue siendo false, por lo que el subproceso 2 reasigna _myResource.

Solución: crear una estática object y bloquear sobre este objeto en lugar de recursos inicializado:

private static readonly object _resourceLock = new object(); 

/*...*/ 

lock(_resourceLock) 
{ 
    /*...*/ 
} 
+0

Buen punto. Realmente no entiendo la secuencia de ejecución que tiene lugar durante la construcción. Veo el problema Creo que también puede haber un problema al asignar un nuevo valor a la cadena. P.EJ. _myResource = "Algún valor nuevo"; – RaoulRubin

+0

Una cosa más para recordar: las constantes de cadena se internan por defecto (es decir, 'object.RefererecnceEquals (s1, s2)' devolverá true para igual constantes de cadena s1 y s2), por lo que es posible hacer cosas como lock ("Mi cadena de caracteres "), pero este diseño no es recomendado por MS. Es por eso que utilizar una variable de cadena como argumento en la instrucción 'lock()' puede generar algunos resultados inesperados. – max

3

Su clase no es seguro:

  1. Se cambia el objeto que está en bloqueo después de haberlo bloqueado
  2. Tiene una propiedad que obtiene el recurso sin bloquearlo.
  3. Se bloquea en un primitive type, que generalmente no es una buena práctica.

Esto debe hacerlo por usted:

public class MyClass 
{ 
    static readonly object _sync = new object(); 
    static string _myResource = ""; 
    static volatile bool _init = false; 

    public MyClass() 
    { 
     if (_init == true) return; 
     lock (_sync) 
     { 
      if (_init == true) return; 
      Thread.Sleep(3000); // some operation that takes a long time 
      _myResource = "Hello World"; 
      _init = true; 
     } 
    } 

    public string MyResource 
    { 
     get 
     { 
      MyClass ret; // Correct 
      lock(_sync) 
      { 
       ret = _myResource; 
      } 
      return ret; 
     } 
    } 
} 

Actualización:
correcta, el recurso estático no debe ser devuelto directamente ... He corregido mi ejemplo en consecuencia.

+0

Usted hace un gran punto. Bloquear en _myResource fue una mala idea. Creo que también hubo un problema mayor: el getter devolvía una referencia al recurso estático. En su ejemplo, no creo que get {lock (_sync) corrija esto. Quizás una mejor solución habría sido bloquear, luego hacer una copia y devolverla. Gracias. – RaoulRubin

+0

La cadena no es un tipo primitivo, pero sigue siendo una mala idea bloquearla, ya que si se interna, su objeto de bloqueo puede quedar expuesto fuera de la clase. –

+0

@Matt, de acuerdo con msdn una cadena es una primitiva: http://msdn.microsoft.com/en-us/library/ms228360%28VS.80%29.aspx – Kiril

0
static string _myResource = ""; 
... 
public MyClass() 
{ 
    ... 
    lock (_myResource) 
    { 
    } 
} 

Debido a la cadena de internados, no debe bloquear un literal de cadena.

ACTUALIZACIÓN

Sí, @RaoulRubin, por lo que si se bloquea en una cadena literal y que literal de cadena es utilizada por múltiples clases entonces usted va a compartir esa cerradura. Esto puede causar un comportamiento inesperado.

+1

FYI Tuve que buscar el internado. Aquí está el MSDN: El tiempo de ejecución de lenguaje común conserva el almacenamiento de cadena manteniendo una tabla, llamada grupo interno, que contiene una única referencia a cada cadena literal única declarada o creada mediante programación en su programa. En consecuencia, una instancia de una cadena literal con un valor particular solo existe una vez en el sistema. – RaoulRubin

+0

Al infractor, no me importaría un comentario. La pregunta es cuáles son los agujeros en el código (desde el punto de vista de la seguridad de un hilo). Publiqué un agujero que el póster estaba realizando, bloqueando las constantes de cadena. ¿Quizás pensaste que el código publicado era una recomendación? –

Cuestiones relacionadas