2010-04-07 17 views
5

Tengo un sistema que toma muestras. Tengo varios hilos de clientes en la aplicación que están interesados ​​en estos ejemplos, pero el proceso real de tomar una muestra solo puede ocurrir en un contexto. Es lo suficientemente rápido como para bloquear el proceso de llamada hasta que se realiza el muestreo, pero lo suficientemente lento como para no querer que varios hilos acumulen solicitudes. Se me ocurrió con este diseño (pelado abajo a los detalles mínimos):¿Es este un diseño de sincronización entrelazado correcto?

public class Sample 
{ 
    private static Sample _lastSample; 
    private static int _isSampling; 

    public static Sample TakeSample(AutomationManager automation) 
    { 
     //Only start sampling if not already sampling in some other context 
     if (Interlocked.CompareExchange(ref _isSampling, 0, 1) == 0) 
     { 
      try 
      { 
       Sample sample = new Sample(); 
       sample.PerformSampling(automation); 
       _lastSample = sample; 
      } 
      finally 
      { 
       //We're done sampling 
       _isSampling = 0; 
      } 
     } 

     return _lastSample; 
    } 

    private void PerformSampling(AutomationManager automation) 
    { 
     //Lots of stuff going on that shouldn't be run in more than one context at the same time 
    } 
} 

Es esto seguro para su uso en el escenario que he descrito?

Respuesta

5

Sí, parece seguro porque int es un tipo atómico aquí. Pero aun así, aconsejo la sustitución

private static int _isSampling; 

con

private static object _samplingLock = new object(); 

y uso:

lock(_samplingLock) 
{ 
    Sample sample = new Sample(); 
    sample.PerformSampling(automation); 
    _lastSample = sample; 
} 

Simplemente porque es el patrón recomendado y también se asegura de todos los accesos a _lastSample es tratado correctamente.

NB: Esperaría una velocidad comparable, el bloqueo utiliza la clase de Monitor administrado que usa enclavamiento interno.

Editar:

echaba de menos el aspecto de back-off, aquí es otra versión:

if (System.Threading.Monitor.TryEnter(_samplingLock)) 
    { 
    try 
    { 
     .... // sample stuff 
    } 
    finally 
    { 
      System.Threading.Monitor.Exit(_samplingLock); 
    } 
    } 
+0

+ 1 para la solución más simple. –

+1

El TryEnter probablemente sea mejor desde la perspectiva del mantenimiento, ya que es probable que más personas estén familiarizadas con Monitor.TryEnter que con Interlocked.CompareExchange. No estoy demasiado preocupado por los gastos generales en este caso, ya que solo estoy tomando muestras unas pocas veces por segundo. Cuando realmente realiza el muestreo, la sobrecarga del proceso de muestreo en sí será bastante más que la primitiva de bloqueo, estoy seguro. –

+0

Me gusta el Monitor.TryEnter, pero ¿no deberíamos usar el ReaderWriterLockSlim en estos días? http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim_members.aspx –

-1

lo general declaran un bool volátil y hacer algo como:

private volatile bool _isBusy; 
private static Sample _lastSample; 

private Sample DoSomething() 
{ 
    lock(_lastSample) 
    { 
     if(_isBusy) 
      return _lastSample; 
     _isBusy = true; 
    } 

    try 
    { 
     _lastSample = new sameple//do something 
    } 
    finally 
    { 
     lock(_lastSample) 
     { 
      _isBusy = false; 
     } 
    } 
    return _lastSample; 
} 
+1

No es necesario declarar el bool volátil si solo lo usa dentro de un candado. Además, es una muy mala práctica bloquear un campo mutable. En su muestra, es posible ingresar dos bloques de bloqueo al mismo tiempo. Cuando se bloquea en un campo mutable, esperaría un comentario detallado sobre por qué no podría doler (tanto) si dos (o tal vez más) hilos entraron en un bloque de bloqueo simultáneamente. –

Cuestiones relacionadas