6
votes

Memory Read Vistibilité Commande de la commande VS Écrire une commande en utilisant un verrouillage à double coffre

J'ai la fonction suivante qui est destinée à "mémoialiser" des fonctions sans argument. Ce qui signifie seulement appeler la fonction une fois, puis renvoyer le même résultat tous les autres moments.

private static Func<T> Memoize<T>(Func<T> func)
{
    var lockObject = new object();
    var value = default(T);
    var inited = false;

    return () => {
        if (inited)
            return value;

        lock (lockObject) {
            if (!inited) {
                value = func();
                inited = true;
            }
        }

        return value;
    };
}


12 commentaires

Pourquoi n'utilisez-vous pas l'intégré < Code> paresseux classe ?


Avec votre code, il peut arriver que Func () soit évalué deux fois si Memoize () est appelé avant le premier Func () retourné.


Ce n'est pas un duplicata. Le problème est le même, mais la solution est différente (ce n'est pas un singleton!).


Ce particulier Réponse dans le duplicata dit "OK", mais vous devriez en effet envisager d'utiliser l'utilisation existante paresseux faire cela.


@Dkoch: Non, cela ne peut pas arriver. Pour que cela se produise, inité devrait être considéré comme faux à l'intérieur la serrure par deux threads distincts, ce qui est clairement impossible (sauf Func lancers).


@Cameron La question que j'ai choisie n'est pas sur le singleton non plus - Verrouillage de la double vérification est le motif utilisé dans les deux questions ... Je suis disposé à ré-ouvrir cette question si quelqu'un peut le mettre à jour pour clarifier pourquoi il n'est pas associé au verrouillage à double vérité.


@ALEXEI: True, mais je pense que l'utilisation du modèle ici est suffisamment spécifique pour justifier sa propre question (appliquer les réponses à cette question à celle-ci nécessite un saut non insignifiant).


J'ai rouvert. L'autre question a demandé si le modèle de verrouillage de la double vérification dans .NET est cassé (ce n'est pas); Cette question demande si son code l'applique correctement. Liés, mais pas en double.


@Douglas - Bon point ... Étrange que personne n'a essayé de mettre en œuvre cela avant :)


Notnull, s'il vous plaît voir si mon édition s'aligne sur vos objectifs sur la question.


@Alexeilevenkov Mon objectif était en effet de recevoir des commentaires sur la mise en œuvre.


@Alexeilevenkov: Cette question introduit de nouvelles considérations qui ne s'appliquaient pas au scénario général. Étant donné que les variables doivent être déclarées comme des locaux (plutôt que des champs), ils ne peuvent pas tirer parti du mot clé volatile.


3 Réponses :


5
votes

Non, car inité code> n'est pas volatile code>. volatile code> vous donne la libération de la mémoire et acquérir des clôtures dont vous avez besoin afin d'établir le bon arrive-avant em> relation.

S'il n'y a pas de clôture de libération avant initée est défini sur true, puis la valeur code> ne peut pas être complètement écrite à l'heure à laquelle un autre thread lit initéd code> et le voit comme vrai, ce qui pourrait entraîner une demi-personne. objet construit étant retourné. De même, s'il y a une clôture de libération, mais aucune clôture d'acquérir correspondante avant de lire initée code> dans le premier chèque, il est possible que l'objet est complètement construit, mais que le noyau de la CPU qui a vu initée code > Comme vrai n'a pas encore vu les effets de mémoire de la valeur code> étant écrits (la cohérence du cache ne nécessite pas nécessairement que les effets des écrivies consécutives sont vus dans l'ordre sur d'autres cœurs). Cela entraînerait une fois potentiellement un objet à moitié construit étant renvoyé. P>

Ceci est, à la manière, une instance du motif de verrouillage à double vérification déjà très bien documenté. P>

Au lieu d'utiliser une Lambda qui capture les variables locales (qui permettra au compilateur générer une classe implicite pour contenir les variables fermées dans des champs non volatils), je suggère de créer explicitement votre propre classe avec un volatile code> Classé pour Valeur Code>. P>

private class Memoized<T>
{
    public T value;
    public volatile bool inited;
}

private static Func<T> Memoize<T>(Func<T> func)
{
    var memoized = new Memoized<T>();

    return () => {
        if (memoized.inited)
            return memoized.value;

        lock (memoized) {
            if (!memoized.inited) {
                memoized.value = func();
                memoized.inited = true;
            }
        }

        return memoized.value;
    };
}


2 commentaires

Je vais simplement le remplacer par une instance de paresseux: Func statique privé Memoize (Func Func) {var paresseux = nouveau paresseux (FUNC); retour () => paresseux.value; } qui devrait faire ce que je voulais. C'est bien de savoir que ces choses peuvent être correctes!


@Notnull, à propos d'être difficile à obtenir de droite: Changer Nouveau paresseux (FUNC) TO Nouveau paresseux (FUNC, VRAI) . Non Nécessaire , mais des documents explicitement ce qu'il fait.



1
votes

Non, ce code n'est pas sûr. Le compilateur est libre de réorganiser les écrivies sur valeur et inité ; Donc, le système de mémoire est donc. Cela signifie qu'un autre thread pourrait voir inité défini sur true tandis que la valeur est toujours à sa valeur par défaut.

Ce modèle est appelé à double coché Verrouillage et est discuté par Albahari sous initialisation paresseuse . La solution recommandée consiste à utiliser la classe intégrée lazy . Une implémentation équivalente serait la suivante: xxx


0 commentaires

2
votes

Je pense que vous feriez mieux d'utiliser la norme paresseux classe pour implémenter les fonctionnalités dont vous avez besoin, comme dans: xxx


2 commentaires

Il semble que le constructeur par défaut (sans Boolean) soit également en sécurité. Selon les documents: "Le mode de sécurité du fil d'une instance paresseux initialisée avec ce constructeur est lazythreadsafetymode.executionandPublication."


@Notnull Oui, mais l'utiliser explicitement dans ce cas fait pour une bonne documentation de cette ligne