Comment implémenter multithread safe singleton en C++11 sans utiliser

maintenant que C++11 a multithreading je me demandais quelle était la bonne façon d'implémenter lazy initialized singleton sans utiliser de mutex(pour des raisons de perf). J'ai inventé ça, mais tbh N'est pas vraiment bon pour écrire du code lockfree, donc je cherche 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 tester, Real singleton n'aurait pas cette fonction.

62
demandé sur Xeo 2012-07-29 22:42:33

5 réponses

C++11 supprime la nécessité d'un verrouillage manuel. L'exécution simultanée doit attendre si une variable locale statique est déjà initialisée.

§6.7 [stmt.dcl] p4

si control entre la déclaration concurremment pendant que la variable est initialisée, l'exécution concurrente doit attendre l'achèvement de l'initialisation.

comme tel, simple ont une fonction static comme ceci:

static Singleton& get() {
  static Singleton instance;
  return instance;
}

cela fonctionnera parfaitement en C++11 (aussi longtemps que le compilateur implémente correctement cette partie de la norme, bien sûr).


bien sûr, le réel réponse correcte est à pas utiliser un singleton, période .

125
répondu Xeo 2018-01-07 01:51:39

pour moi, la meilleure façon d'implémenter un singleton en utilisant 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.
}
29
répondu GutiMac 2018-01-07 01:50:22

il est difficile de lire votre approche car vous n'utilisez pas le code comme prévu... c'est le modèle commun pour un singleton appelle instance() pour obtenir la seule instance, puis l'utiliser (d'ailleurs, si vous voulez vraiment un singleton, aucun constructeur doit être publique).

en tout cas, je ne pense pas que votre approche est sûre, considérez que deux threads essayer d'acquérir le singleton, le premier qui obtient de mettre à jour le drapeau sera le seul qui initialise, mais le La fonction initialize sortira tôt sur la seconde, et ce thread pourrait continuer à utiliser le singleton avant le premier thread a fait le tour pour terminer l'initialisation.

la sémantique de votre initialize est cassée. Si vous essayez de décrire / document le comportement de la fonction, vous aurez du plaisir, et finira par décrire la mise en œuvre plutôt qu'une simple opération. La documentation est généralement un moyen simple de vérifier une conception/algorithme: si vous finissez par décrire comment plutôt que quoi , alors vous devriez revenir à la conception. En particulier, il n'existe aucune garantie qu'après initialize complète l'objet a effectivement été initialisé (seulement si la valeur retournée est true , et parfois si c'est false , mais pas toujours).

2
répondu David Rodríguez - dribeas 2012-07-29 20:07:13

IMHO, la meilleure façon d'implémenter singletons est avec un modèle "double-check, single-lock", que vous pouvez implémenter de manière fiable en C++ 11: Le Verrouillage À Double Contrôle Est Fixé En C++11 Ce modèle est rapide dans le cas déjà créé, ne nécessitant qu'une seule comparaison de pointeur, 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 construction-ordre pour les variables locales statiques est local filetage d'initialisation des variables statiques-sans danger en C++11? donc vous êtes en sécurité en utilisant ce modèle. Cependant, Visual Studio 2013 ne le supporte pas encore: - ( voir la ligne" magic statics "sur cette page , donc si vous utilisez VS2013 vous devez le faire vous-même.

Malheureusement, rien n'est jamais simple. Le code échantillon référencé pour le motif ci-dessus ne peut pas être appelé à partir de L'initialisation CRT, parce que la statique std:: mutex a un constructeur, et n'est donc pas garanti d'être initialisé avant le premier appel pour obtenir le singleton, si ledit appel est un effet secondaire de L'initialisation CRT. Pour obtenir autour de que , vous devez utiliser, pas un mutex, mais un pointeur-à-mutex, qui est garanti pour être zéro-initialisé avant que L'initialisation CRT commence. Ensuite, vous devrez utiliser std::atomic::compare_exchange_thnet pour créer et utiliser le mutex.

je suppose que le La sémantique de l'initialisation locale-statique-sans fil C++ 11 Fonctionne même lorsqu'elle est appelée lors de l'initialisation CRT.

donc si vous avez la sémantique d'initialisation C++ 11 thread-safe local-static-initialization disponible, utilisez-les. Si ce n'est pas le cas, vous avez du travail à faire, même plus si vous voulez que votre singleton soit sécurisé pendant l'initialisation CRT.

2
répondu Wheezil 2017-05-23 12:18:20
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 T);
        });
        return *m_ins.get();
    }
};
1
répondu Pooja Kuntal 2018-05-15 09:32:33