Question

Récemment, je suis refactoring une partie de mon code C # et j'ai trouvé quelques pratiques revérifié verrouillage en cours. Je ne savais pas qu'il était une mauvaise pratique à l'époque et je veux vraiment vous en débarrasser.

Le problème est que j'ai une classe qui doit être initialisé paresseusement et souvent accessible par beaucoup de fils. Je ne veux également pas déplacer l'initialisation à un initialiseur statique, parce que je suis l'intention d'utiliser une référence faible pour maintenir l'objet initialisées de rester trop longtemps dans la mémoire. Cependant, en cas de besoin, je veux « faire revivre » l'objet assurant que ce qui se passe d'une manière thread-safe.

Je me demandais si vous utilisez un ReaderWriterLockSlim en C # et entrez un UpgradeableReadLock avant le premier chèque, puis le cas échéant entrer un verrou d'écriture pour l'initialisation serait une solution acceptable. Voici ce que je vais avoir à l'esprit:

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

Mon point est qu'aucun autre thread devrait essayer d'initialiser l'élément une fois de plus, alors que de nombreux fils doivent lire simultanément une fois que la valeur est initialisé. Le verrou de lecture doit être mis à niveau bloquer tous les lecteurs si le verrou d'écriture est acquise, donc tout l'objet est en cours d'initialisation, le comportement sera semblable à avoir une déclaration de verrouillage où le verrou de lecture commence upgradable. Après l'initialisation du verrou en lecture améliorables permettre plusieurs threads par conséquent, la perte de performance d'attente chaque thread ne sera pas présent.

Je lis aussi un article disant que les causes volatiles des barrières de mémoire à insérer automatiquement avant de lire et après écriture, donc je suppose qu'une seule barrière définie manuellement entre la lecture et l'écriture sera suffisant pour faire en sorte que l'objet _valueReference est lu correctement. Je serai heureux de recevoir vos conseils et critiques pour l'utilisation de cette approche.

Était-ce utile?

La solution

Pour souligner le point @Mannimarco fait: si tel est le seul point d'accès à la valeur, et il semble de cette façon, votre installation entière ReaderWriterLockSlim est pas mieux qu'une simple approche Monitor.Enter / Monitor.Leave. Il est beaucoup plus compliqué cependant.

Je crois donc que le code suivant est équivalent en fonction et de l'efficacité:

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

Autres conseils

Un avertissement: un seul thread peut entrer en mode UpgradeableReadLock à la fois. Consultez ReaderWriterLockSlim . Donc, si les discussions s'empilent alors que le premier fil entre le mode d'écriture et crée l'objet, vous aurez un goulot de bouteille jusqu'à ce que la sauvegarde est (je l'espère) résolue. Je suggère sérieusement à l'aide d'un initialiseur statique, il vous rendra la vie plus facile.

EDIT: En fonction de la fréquence à laquelle les besoins des objets à être recréées, je suggère en fait en utilisant la classe Monitor et son attente et Pulse méthodes. Si la valeur doit être recréée, ont les fils d'attente sur un objet et un autre objet d'impulsion pour laisser un savoir de thread de travail dont il a besoin de se réveiller et créer un nouvel objet. Une fois que l'objet a été créé, PulseAll permettra à tous les fils de lecteur de se réveiller et de saisir la nouvelle valeur. (En théorie)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top