2011-08-23 14 views
27

que estoy tratando de crear propiedades de seguridad de rosca en C# y que quieren asegurarse de que estoy en el camino correcto - esto es lo que he hecho -rosca Propiedades seguras en C#

private readonly object AvgBuyPriceLocker = new object(); 
private double _AvgBuyPrice; 
private double AvgBuyPrice 
{ 
    get 
    { 
     lock (AvgBuyPriceLocker) 
     { 
      return _AvgBuyPrice; 
     } 
    } 
    set 
    { 
     lock (AvgBuyPriceLocker) 
     { 
      _AvgBuyPrice = value; 
     } 
    } 
} 

La lectura de esta publicación, parecería como si esto no es la forma correcta de hacerlo -

C# thread safety with get/set

sin embargo, este artículo parece sugerir lo contrario,

http://www.codeproject.com/KB/cs/Synchronized.aspx

¿Alguien tiene una respuesta más definitiva?

Editar:

La razón por la que yo quiero hacer el captador/definidor de esta propiedad es b/c que realmente quiero para disparar un evento cuando se establece - por lo que el código podría ser en realidad como este -

public class PLTracker 
{ 

    public PLEvents Events; 

    private readonly object AvgBuyPriceLocker = new object(); 
    private double _AvgBuyPrice; 
    private double AvgBuyPrice 
    { 
     get 
     { 
      lock (AvgBuyPriceLocker) 
      { 
       return _AvgBuyPrice; 
      } 
     } 
     set 
     { 
      lock (AvgBuyPriceLocker) 
      { 
       Events.AvgBuyPriceUpdate(value); 
       _AvgBuyPrice = value; 
      } 
     } 
    } 
} 

public class PLEvents 
{ 
    public delegate void PLUpdateHandler(double Update); 
    public event PLUpdateHandler AvgBuyPriceUpdateListener; 

    public void AvgBuyPriceUpdate(double AvgBuyPrice) 
    { 
     lock (this) 
     { 
      try 
      { 
       if (AvgBuyPriceUpdateListener!= null) 
       { 
        AvgBuyPriceUpdateListener(AvgBuyPrice); 
       } 
       else 
       { 
        throw new Exception("AvgBuyPriceUpdateListener is null"); 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.Message); 
      } 
     } 
    } 
} 

soy bastante nuevo para hacer mi hilo código de seguridad así que por favor libre de decirme si voy sobre él en el camino totalmente equivocado!

Will

+1

Está bien. No lo veo, por qué piensas que no es así. La respuesta de SO vinculada no indica que es una mala idea hacerlo así. –

+0

var propiedad = new ConcurrentValue (); property.ReadValue (x => { // use este valor seguro de subproceso x }); property.WriteValue (() => { // complejo cómputo de bloqueo que consume mucho tiempo return "Computed"; }); } puede crear una clase ConcurrentValue para usar como un titular de valor de propiedad con funcionalidad de bloqueo para escribir y hacer múltiples lecturas exclusivamente - Object.Property.Write ((= => cálculo que consume tiempo) // bloquea Object.Property.Read (x =>) –

Respuesta

18

Puesto que usted tiene un valor primitivo de este bloqueo no tendrán ningún problema - el problema en la otra cuestión es que el valor de la propiedad era una clase más compleja (un tipo de referencia mutable) - el bloqueo protegerá el acceso y recuperando la instancia del doble valor mantenido por su clase.

Si el valor de su propiedad es un tipo de referencia mutable, el bloqueo no protegerá de cambiar la instancia de clase una vez recuperada utilizando sus métodos, que es lo que el otro póster quería que hiciera.

6

lectura y la escritura de dobles es atómica de todos modos ( source) lectura y escritura de dobles no es atómico y por lo que sería necesario para proteger el acceso a un doble uso de una cerradura, sin embargo, para muchos tipos de lectura y escritura es atómico y por lo que el siguiente sería igual de seguro:

private float AvgBuyPrice 
{ 
    get; 
    set; 
} 

Mi punto es que la seguridad de rosca es más complejo que la simple protección de cada una de sus propiedades. Para dar un ejemplo sencillo supongamos que tengo dos propiedades AvgBuyPrice y StringAvgBuyPrice:

private string StringAvgBuyPrice { get; set; } 
private float AvgBuyPrice { get; set; } 

Y supongo que actualizar el precio de compra promedio de esta manera:

this.AvgBuyPrice = value; 
this.StringAvgBuyPrice = value.ToString(); 

Esto claramente no es hilo de seguridad y protección de propiedades en forma individual la forma anterior no ayudará en absoluto. En este caso, el bloqueo debe realizarse a un nivel diferente en lugar de a nivel de propiedad.

+4

De acuerdo con su fuente de MSDN, los dobles * no * se garantiza que sean atómicos: "Las lecturas y escrituras de otros tipos, incluidos long, ulong, double y decimal, así como los tipos definidos por el usuario, no son garantizado para ser atómico ". – JeremyDWill

+0

@JeremyDWill Bien visto: he editado mi pregunta, sin embargo, creo que mi punto sigue en pie. – Justin

+0

Estoy de acuerdo con JeremyDWill, debe considerar que el doble es un número de 64 bits, por lo que cuando se trabaja con máquinas de 32 bits, las operaciones de lectura y escritura se pueden realizar en más de un paso. –

16

Las cerraduras, como las has escrito, no tienen sentido. El hilo que lee la variable, por ejemplo, será:

  1. Adquiera el candado.
  2. Lea el valor.
  3. Suelta la cerradura.
  4. Use el valor de lectura de alguna manera.

No hay nada que impida que otro hilo de modificar el valor después del paso 3. Como el acceso variable en .NET es atómica (ver advertencia más abajo), el bloqueo no es en realidad el logro mucho aquí: la simple adición de una sobrecarga. Contraste con el ejemplo desbloqueado:

  1. Lea el valor.
  2. Use el valor de lectura de alguna manera.

Otro hilo puede alterar el valor entre los pasos 1 y 2 y esto no es diferente del ejemplo bloqueado.

Si desea asegurarse de Estado no cambia cuando usted está haciendo algún tipo de procesamiento, debe leer el valor y hacer el procesamiento utilizando ese valor dentro del Contex de la cerradura:

  1. adquirir el bloqueo .
  2. Lea el valor.
  3. Use el valor de lectura de alguna manera.
  4. Suelta la cerradura.

Habiendo dicho eso, hay casos en los que necesita bloquear cuando se accede a una variable. Por lo general, esto se debe a razones del procesador subyacente: una variable double no se puede leer o escribir como una sola instrucción en una máquina de 32 bits, por lo que debe bloquear (o usar una estrategia alternativa) para garantizar que no leer.

10

La seguridad del subproceso no es algo que deba agregar a sus variables, es algo que debe agregar a su "lógica". Si agrega bloqueos a todas sus variables, su código no será necesariamente seguro para subprocesos, pero será lento como el infierno. Para escribir un programa seguro para subprocesos, mire su código y decida dónde podrían estar usando varios subprocesos utilizando los mismos datos/objetos. Agregue cerraduras u otras medidas de seguridad a todos esos lugares críticos.

Por ejemplo, suponiendo que el siguiente bit de pseudo código:

void updateAvgBuyPrice() 
{ 
    float oldPrice = AvgBuyPrice; 
    float newPrice = oldPrice + <Some other logic here> 
    //Some more new price calculation here 
    AvgBuyPrice = newPrice; 
} 

Si este código se llama desde varios subprocesos al mismo tiempo, su lógica de bloqueo no tiene ningún uso. Imagine el hilo A obteniendo AvgBuyPrice y haciendo algunos cálculos. Ahora, antes de que termine, el hilo B también obtiene el AvgBuyPrice y comienza los cálculos. Mientras tanto, el subproceso A está terminado y asignará el nuevo valor a AvgBuyPrice. Sin embargo, momentos después, se sobrescribirá con el subproceso B (que todavía usaba el valor anterior) y el trabajo del subproceso A se perdió por completo.

Entonces, ¿cómo se soluciona esto? Si tuviéramos que utilizar cerraduras (que sería la solución más fea y más lento, pero la más fácil si estás empezando con multihilo), tenemos que poner toda la lógica que cambia AvgBuyPrice en las cerraduras:

void updateAvgBuyPrice() 
{ 
    lock(AvgBuyPriceLocker) 
    { 
     float oldPrice = AvgBuyPrice; 
     float newPrice = oldPrice + <Some other code here> 
     //Some more new price calculation here 
     AvgBuyPrice = newPrice; 
    } 
} 

Ahora , si el subproceso B quiere hacer los cálculos mientras el subproceso A aún está ocupado, esperará hasta que finalice el subproceso A y luego realizará su trabajo utilizando el nuevo valor. Tenga en cuenta, sin embargo, que cualquier otro código que también modifique AvgBuyPrice también debe bloquear AvgBuyPriceLocker mientras está funcionando.

Aún así, esto será lento si se usa con frecuencia.Los bloqueos son costosos y hay muchos otros mecanismos para evitar bloqueos, solo busque algoritmos sin bloqueo.

+3

Para agregar al comentario de Mart, Joseph Albahari ha escrito [un excelente libro en línea] (http://www.albahari.com/threading/) con respecto a todos los aspectos de enhebrado. El libro está disponible como HTML o PDF. –