Domanda

Di recente ho rifacato parte del mio codice C# e ho trovato alcune pratiche di bloccaggio a doppio controllo. All'epoca non sapevo che fosse una brutta pratica e voglio davvero sbarazzarmene.

Il problema è che ho una classe che dovrebbe essere pigramente inizializzata e frequentemente accessibile da molti thread. Inoltre, non voglio spostare l'inizializzazione in un inizializzatore statico, perché sto pianificando di utilizzare un riferimento debole per impedire all'oggetto inizializzato di rimanere troppo a lungo nella memoria. Tuttavia, se necessario, voglio "rilanciare" l'oggetto assicurando che ciò accada in modo sicuro.

Mi chiedevo se usare un lettorewriterlockslim in C# e inserire un aggiornamentoeablereadlock prima del primo controllo, quindi se necessario, inserire un blocco di scrittura per l'inizializzazione sarebbe una soluzione accettabile. Ecco cosa sto pensando:

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    
    }
}

Il mio punto è che nessun altro thread dovrebbe provare a inizializzare ancora una volta l'articolo, mentre molti thread dovrebbero leggere contemporaneamente una volta inizializzato il valore. Il blocco di lettura aggiornabile dovrebbe bloccare tutti i lettori se viene acquisito il blocco della scrittura, quindi mentre l'oggetto viene inizializzato, il comportamento sarà simile all'assunzione di una dichiarazione di blocco in cui inizia il blocco di lettura aggiornabile. Dopo l'inizializzazione, il blocco di lettura aggiornabile consentirà più thread, pertanto non sarà presente il colpo delle prestazioni di attesa ogni thread.

Ho anche letto un articolo qui Dire che volatile causa l'inserimento automatico delle barriere di memoria prima della lettura e dopo la scrittura, quindi suppongo che solo una barriera definita manualmente tra la lettura e la scrittura sarà sufficiente per garantire che l'oggetto _valueReference sia letta correttamente. Apprezzerò volentieri i tuoi consigli e le tue critiche per l'uso di questo approccio.

È stato utile?

Soluzione

Per enfatizzare il punto che @Mannimarco fa: se questo è l'unico punto di accesso al valore, e sembra così, allora l'installazione dell'installazione di ReaderWriterlockslim non è migliore di un semplice monitor.Enter / Monitor.Leave Approccio. È molto più complicato però.

Quindi credo che il seguente codice sia equivalente alla funzione e all'efficienza:

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; 
    }
  }    
}

Altri suggerimenti

Un avviso: solo un singolo thread può inserire la modalità aggiornamentoeablereadlock alla volta. Guardare Readerwriterlockslim. Quindi, se i fili si accumulano mentre il primo thread entra nella modalità di scrittura e crea l'oggetto, avrai un collo di bottiglia fino a quando il backup è (si spera) risolto. Suggerirei seriamente di usare un inizializzatore statico, ti semplificherà la vita.

EDIT: a seconda della frequenza con cui l'oggetto deve essere ricreato, suggerirei effettivamente di utilizzare la classe Monitor e i suoi metodi di attesa e impulsi. Se il valore deve essere ricreato, chiedi ai thread di attendere un oggetto e impulca un altro oggetto per far sapere a un thread del lavoratore che deve svegliarsi e creare un nuovo oggetto. Una volta creato l'oggetto, Pulseall consentirà a tutti i thread del lettore di svegliarsi e prendere il nuovo valore. (in teoria)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top