Pregunta

Recientemente he estado refactorizando parte de mi código C# y encontré algunas prácticas de bloqueo a doble verificación. No sabía que era una mala práctica en ese entonces y realmente quiero deshacerme de ella.

El problema es que tengo una clase que debe inicializarse perezosamente y acceder con frecuencia por muchos hilos. Tampoco quiero mover la inicialización a un inicializador estático, porque estoy planeando usar una referencia débil para evitar que el objeto inicializado permanezca demasiado tiempo en la memoria. Sin embargo, si es necesario, quiero 'revivir' el objeto asegurando que esto suceda de manera segura de hilo.

Me preguntaba si usar un ReaderWriterLockSlim en C# e ingresar un UpdateAleLeArtLock antes de la primera comprobación, y luego, si es necesario, ingrese un bloqueo de escritura para la inicialización sería una solución aceptable. Esto es lo que estoy teniendo en mente:

public class LazyInitialized
{
    private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

    private volatile WeakReference _valueReference = new WeakReference(null);
    public MyType Value
    {
        get
        {
            MyType value = _valueReference.Target as MyType;
            _lock.EnterUpgradeableReadLock();
            try
            {
                if (!_valueReference.IsAlive) // needs initializing
                {
                    _lock.EnterWriteLock();
                    try
                    {
                        if (!_valueReference.IsAlive) // check again
                        {
                            // prevent reading the old weak reference
                            Thread.MemoryBarrier(); 
                            _valueReference = new WeakReference(value = InitializeMyType());
                        }
                    }
                    finally
                    {
                        _lock.ExitWriteLock();
                    }
                }
            }
            finally
            {
                _lock.ExitUpgradeableReadLock();
            }
            return value;
        }       
    }

    private MyType InitializeMyType()
    {
        // code not shown    
    }
}

Mi punto es que ningún otro hilo debe intentar inicializar el elemento una vez más, mientras que muchos hilos deben leer simultáneamente una vez que se inicialice el valor. El bloqueo de lectura actualizable debe bloquear a todos los lectores si se adquiere el bloqueo de escritura, por lo tanto, mientras se inicializa el objeto, el comportamiento será similar a tener una declaración de bloqueo donde comienza el bloqueo de lectura actualizable. Después de la inicialización, el bloqueo de lectura actualizable permitirá múltiples hilos, por lo tanto, el éxito de rendimiento de esperar cada hilo no estará presente.

También leo un artículo aquí Decir que volátiles hace que las barreras de memoria se inserten automáticamente antes de leer y después de escribir, por lo que supongo que solo una barrera definida manualmente entre la lectura y la escritura será suficiente para garantizar que el objeto _valueeference se lea correctamente. Con mucho gusto apreciaré sus consejos y críticas por usar este enfoque.

¿Fue útil?

Solución

Para enfatizar el punto, @Mannimarco hace: si este es el único punto de acceso al valor, y se ve de esa manera, entonces toda su configuración de ReaderWriterLockSlim no es mejor que un simple enfoque monitor.enter / monitor.leave. Sin embargo, es mucho más complicado.

Por lo tanto, creo que el siguiente código es equivalente en función y eficiencia:

private WeakReference _valueReference = new WeakReference(null);
private object _locker = new object();

public MyType Value
{    
  get
  {    
    lock(_locker)  // also provides the barriers
    {
        value = _valueReference.Target;

        if (!_valueReference.IsAlive)
        {
            _valueReference = new WeakReference(value = InitializeMyType());
        }
        return value; 
    }
  }    
}

Otros consejos

Una advertencia: solo un solo hilo puede ingresar al modo UpgrateAleLeReadLock a la vez. Verificar ReaderWriterLockSlim. Entonces, si los hilos se acumulan mientras el primer hilo ingresa al modo de escritura y crea el objeto, tendrá un cuello de botella hasta que la copia de seguridad esté (con suerte) se resuelva. Sugeriría seriamente usar un inicializador estático, le facilitará la vida.

EDITAR: Dependiendo de la frecuencia con la que el objeto sea necesario recrear, en realidad sugeriría usar la clase de monitor y sus métodos de espera y pulso. Si el valor necesita ser recreado, haga que los hilos esperen en un objeto y pulse otro objeto para que un hilo de trabajador sepa que necesita despertarse y crear un nuevo objeto. Una vez que se haya creado el objeto, PulseAll permitirá que todos los hilos del lector se despierten y obtengan el nuevo valor. (En teoria)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top