J'ai donc vu beaucoup d'articles affirmant que le verrouillage double C++, couramment utilisé pour empêcher plusieurs threads d'initialiser un singleton créé paresseusement, est cassé. Code de verrouillage revérifié normale se lit comme suit:Qu'est-ce qui ne va pas avec ce correctif pour un double verrouillage?
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 par exemple l'affectation - le compilateur est libre d'affecter l'objet puis attribuez-lui le pointeur, ou pour régler le pointeur à l'endroit où il sera alloué, puis l'allouer. Le dernier cas casse l'idiome - un thread peut allouer la mémoire et assigner le pointeur mais pas exécuter le constructeur du singleton avant qu'il ne soit mis en sommeil - alors le deuxième thread verra que l'instance n'est pas nulle et essaiera de le renvoyer , même si elle n'a pas encore été construite.
I saw a suggestion pour utiliser un thread 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 fil 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 que se passe-t-il si nous venons d'utiliser un bool statique local?:
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 cela ne fonctionnerait-il pas? Même si sync_check devait être lu par un thread quand il est assigné dans un autre, la valeur du garbage sera toujours non nulle et donc vraie. This Dr. Dobb's article affirme que vous devez vous verrouiller car vous ne remporterez jamais une bataille avec le compilateur en raison d'instructions de réorganisation. Ce qui me fait penser que cela ne doit pas fonctionner pour une raison quelconque, mais je n'arrive pas à comprendre pourquoi. Si les exigences sur les points de séquence sont aussi faibles que l'article de Dr. Dobb me le fait croire, je ne comprends pas pourquoi aucun code après que le verrou n'a pas pu être réorganisé pour être devant le verrou. Ce qui rendrait C++ multithreading période cassée. Je pense que je pourrais voir le compilateur autorisé à réordonner spécifiquement sync_check pour être avant le verrou parce que c'est une variable locale (et même si c'est statique nous ne retournons pas une référence ou un pointeur vers lui) - mais alors pourrait encore être résolu en en faisant un membre statique (effectivement global) à la place.
Alors, cela fonctionnera-t-il ou non? Pourquoi?
Le problème est que la variable peut être affectée avant l'exécution (ou l'achèvement) du constructeur, et non avant l'allocation de l'objet. – kdgregory
Merci, corrigé. Je me suis totalement trompé sur les conditions de course. –
Oui, vous avez raison, le C++ actuel est vraiment "multithread broken period". en considérant la norme seulement. Les vendeurs de compilateurs fournissent habituellement des moyens autour de ceci, donc les résultats pratiques ne sont pas si terribles. – Suma