Question

Je l'ai vu beaucoup d'articles qui prétendent maintenant que le double verrouillage contrôlé C de, couramment utilisé pour empêcher plusieurs threads d'essayer d'initialiser un singleton créé paresseusement, est cassé. Code de verrouillage revérifié normale se lit comme suit:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!instance)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;
        }

        return *instance;
    }
};

Le problème est apparemment la ligne assignant par exemple - le compilateur est libre d'affecter l'objet puis attribuez-lui le pointeur, ou pour régler le pointeur à l'endroit où il est attribué, puis l'affecter. Dans ce dernier cas casse l'idiome - un fil peut affecter la mémoire et attribuer le pointeur mais pas exécuter le constructeur de singleton avant qu'il ne soit mis en sommeil - puis le second fil verra que l'instance n'est pas nulle et essayer de le retourner , même si elle n'a pas été construit encore.

a vu une suggestion d'utiliser un fil booléen local et vérifier qu'au lieu de instance. Quelque chose comme ceci:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;
    static boost::thread_specific_ptr<int> _sync_check;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!_sync_check.get())
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            // Any non-null value would work, we're really just using it as a
            // thread specific bool.
            _sync_check = reinterpret_cast<int*>(1);
        }

        return *instance;
    }
};

De cette façon, chaque thread finit par vérifier si l'instance a été créé une fois, mais arrête après cela, ce qui entraîne une baisse de performance, mais encore loin d'être aussi mauvais que le verrouillage de chaque appel. Mais si nous avons utilisé une bool statique locale:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static bool sync_check = false;
        static singleton* instance;

        if(!sync_check)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            sync_check = true;
        }

        return *instance;
    }
};

Pourquoi pas ce travail? Même si sync_check devait être lu par un fil quand il est affecté dans une autre étant la valeur des déchets sera toujours non nul et donc vrai. Cet article du Dr Dobb prétend que vous devez verrouiller parce que vous ne serez jamais gagner une bataille avec le compilateur sur les instructions de remise en ordre. Ce qui me fait penser cela ne doit pas travailler pour une raison quelconque, mais je ne peux pas comprendre pourquoi. Si les exigences sur les points de séquence sont aussi lose que l'article du Dr Dobb me fait croire, je ne comprends pas pourquoi any code après la serrure ne pouvait pas être réorganisés pour être avant la serrure. Ce qui rendrait C ++ multithreading période cassé.

Je suppose que je pouvais voir le compilateur étant autorisé à spécifiquement réordonner sync_check être avant la serrure, car il est une variable locale (et même si elle est statique, nous ne sommes pas retourner une référence ou pointeur vers elle) - mais cela pourrait encore être résolu en le rendant à la place un membre statique (efficace global).

Alors cela fonctionnera ou non? Pourquoi?

Était-ce utile?

La solution

Votre solution ne résout rien puisque les écritures à sync_check et par exemple peut être fait sur commande sur le CPU. A titre d'exemple imaginer les deux premiers appels à l'instance se produisent à peu près en même temps sur deux processeurs différents. Le premier thread va acquérir le verrou, initialiser le pointeur et réglez sync_check sur true, dans cet ordre, mais le processeur peut changer l'ordre des écritures à la mémoire. Sur l'autre CPU alors il est possible pour le second fil pour vérifier sync_check, voir qu'il est vrai, mais par exemple ne peut pas encore être écrit à la mémoire. Voir Lockless Considérations de programmation pour Xbox 360 et Microsoft Windows pour plus de détails.

La solution sync_check spécifique de fil que vous mentionnez devraient alors travailler (en supposant que vous initialisez votre pointeur à 0).

scroll top