Maintenant que C++ 11 est multithread, je me demandais quelle était la bonne façon de mettre en œuvre un singleton initialisé paresseux sans utiliser de mutex (pour des raisons de perfection). Je suis venu avec cela, mais tbh je ne suis pas très bon pour écrire du code sans verrou, alors je suis à la recherche de meilleures solutions.
// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
# include <atomic>
# include <thread>
# include <string>
# include <iostream>
using namespace std;
class Singleton
{
public:
Singleton()
{
}
static bool isInitialized()
{
return (flag==2);
}
static bool initizalize(const string& name_)
{
if (flag==2)
return false;// already initialized
if (flag==1)
return false;//somebody else is initializing
if (flag==0)
{
int exp=0;
int desr=1;
//bool atomic_compare_exchange_strong(std::atomic<T>* obj, T* exp, T desr)
bool willInitialize=std::atomic_compare_exchange_strong(&flag, &exp, desr);
if (! willInitialize)
{
//some other thread CASed before us
std::cout<<"somebody else CASed at aprox same time"<< endl;
return false;
}
else
{
initialize_impl(name_);
assert(flag==1);
flag=2;
return true;
}
}
}
static void clear()
{
name.clear();
flag=0;
}
private:
static void initialize_impl(const string& name_)
{
name=name_;
}
static atomic<int> flag;
static string name;
};
atomic<int> Singleton::flag=0;
string Singleton::name;
void myThreadFunction()
{
Singleton s;
bool initializedByMe =s.initizalize("1701");
if (initializedByMe)
s.clear();
}
int main()
{
while (true)
{
std::thread t1(myThreadFunction);
std::thread t2(myThreadFunction);
t1.join();
t2.join();
}
return 0;
}
Notez que clear()
est juste pour le test, le vrai singleton n'aurait pas cette fonction.
C++ 11 supprime le besoin de verrouillage manuel. L'exécution simultanée doit attendre si une variable locale statique est déjà en cours d'initialisation.
§6.7 [stmt.dcl] p4
Si le contrôle entre la déclaration simultanément alors que la variable est en cours d'initialisation, l'exécution simultanée doit attendre la fin de l'initialisation.
En tant que tel, simple a une fonction static
comme ceci:
static Singleton& get() {
static Singleton instance;
return instance;
}
Cela fonctionnera parfaitement en C++ 11 (à condition que le compilateur implémente correctement cette partie de la norme, bien sûr).
Bien sûr, la réponse correcte réelle consiste à et non utiliser un singleton, période.
Pour moi, le meilleur moyen d'implémenter un singleton à l'aide de C++ 11 est:
class Singleton {
public:
static Singleton& Instance() {
// Since it's a static variable, if the class has already been created,
// it won't be created again.
// And it **is** thread-safe in C++11.
static Singleton myInstance;
// Return a reference to our instance.
return myInstance;
}
// delete copy and move constructors and assign operators
Singleton(Singleton const&) = delete; // Copy construct
Singleton(Singleton&&) = delete; // Move construct
Singleton& operator=(Singleton const&) = delete; // Copy assign
Singleton& operator=(Singleton &&) = delete; // Move assign
// Any other public methods.
protected:
Singleton() {
// Constructor code goes here.
}
~Singleton() {
// Destructor code goes here.
}
// And any other protected methods.
}
IMHO, le meilleur moyen d'implémenter des singletons est d'utiliser un modèle de "double vérification, simple verrouillage" que vous pouvez implémenter de manière portable dans C++ 11: le verrouillage à double contrôle est corrigé dans C++ 11 Ceci pattern est rapide dans le cas déjà créé, ne nécessitant qu'une comparaison de pointeur unique, et sûr dans le cas de la première utilisation.
Comme mentionné dans la réponse précédente, C++ 11 garantit la sécurité de l'ordre de construction pour les variables locales statiques l'initialisation des variables statiques locales est-elle thread-safe en C++ 11? vous êtes donc sûr d'utiliser ce modèle. Cependant, Visual Studio 2013 ne le prend pas encore en charge : --- (Voir la rangée "Statistiques statiques" sur cette page , donc si vous utilisez VS2013, vous devez le faire vous-même.
Malheureusement, rien n'est jamais simple. Le exemple de code référencé pour le modèle ci-dessus ne peut pas être appelé à partir de l'initialisation du CRT, car le std :: mutex statique a un constructeur et ne peut donc pas être initialisé avant le premier appel du singleton, si cet appel est un effet secondaire de l'initialisation du tube cathodique. Pour contourner cela , vous devez utiliser non pas un mutex, mais un pointeur sur mutex, dont l'initialisation à zéro est garantie avant le CRT. l'initialisation commence. Ensuite, vous devrez utiliser std :: atomic :: compare_exchange_strong pour créer et utiliser le mutex.
Je pars du principe que la sémantique d'initialisation de la statique locale et de sécurité du thread C++ 11 fonctionne même lorsqu'elle est appelée lors de l'initialisation de la TRC.
Par conséquent, si vous disposez de la sémantique d'initialisation de la statique locale et de la sécurité du thread C++ 11, utilisez-la. Sinon, vous avez encore du travail à faire, même plus si vous voulez que votre singleton soit thread-safe lors de l'initialisation du tube cathodique.
Il est difficile de lire votre approche car vous n’utilisez pas le code comme prévu ... c’est-à-dire que le modèle courant pour un singleton appelle instance()
pour obtenir l’instance unique, puis l’utilisez (aussi, si vous voulez vraiment un singleton, aucun constructeur ne devrait être public).
En tout cas, je ne pense pas que votre approche soit sûre, considérez que deux threads essaient d’acquérir le singleton, le premier qui arrive à mettre à jour le drapeau sera le seul à initialiser, mais la fonction initialize
sortira tôt sur le second, et ce thread pourrait continuer à utiliser le singleton avant le premier thread parvint à terminer l'initialisation.
La sémantique de votre initialize
est cassée. Si vous essayez de décrivez/document le comportement de la fonction, vous aurez du plaisir, et vous finirez par décrire la mise en oeuvre plutôt que par une simple opération. La documentation est généralement un moyen simple de vérifier une conception/un algorithme: si vous décrivez comment plutôt que quoi, vous devriez revenir à la conception. En particulier, rien ne garantit qu’après la finalisation de initialize
, l’objet a bien été initialisé (uniquement si la valeur renvoyée est true
, et parfois si false
, mais pas toujours).
#pragma once
#include <memory>
#include <mutex>
namespace utils
{
template<typename T>
class Singleton
{
private:
Singleton<T>(const Singleton<T>&) = delete;
Singleton<T>& operator = (const Singleton<T>&) = delete;
Singleton<T>() = default;
static std::unique_ptr<T> m_instance;
static std::once_flag m_once;
public:
virtual ~Singleton<T>() = default;
static T* getInstance()
{
std::call_once(m_once, []() {
m_instance.reset(new T);
});
return m_instance.get();
}
template<typename... Args>
static T* getInstance2nd(Args&& ...args)
{
std::call_once(m_once, [&]() {
m_instance.reset(new T(std::forward<Args>(args)...));
});
return m_instance.get();
}
};
template<typename T> std::unique_ptr<T> Singleton<T>::m_instance;
template<typename T> std::once_flag Singleton<T>::m_once;
}
Cette version est conforme au principe de concurrence simultanée, où la prise en charge de la norme c ++ 11 n’est pas garantie. Il offre également un moyen flexible d’instancier l’instance "possédée". Même si la magie statique Word suffit en c ++ 11 et plus, le développeur peut avoir la nécessité d’avoir beaucoup plus de contrôle sur la création de l’instance.
template<class T>
class Resource
{
Resource<T>(const Resource<T>&) = delete;
Resource<T>& operator=(const Resource<T>&) = delete;
static unique_ptr<Resource<T>> m_ins;
static once_flag m_once;
Resource<T>() = default;
public :
virtual ~Resource<T>() = default;
static Resource<T>& getInstance() {
std::call_once(m_once, []() {
m_ins.reset(new Resource<T>);
});
return *m_ins.get();
}
};