Jak zaimplementować multithread safe singleton w C++11 bez użycia

Teraz, gdy C++11 ma wielowątkowość, zastanawiałem się, jak poprawnie zaimplementować leniwy inicjalizowany singleton bez używania muteksów(ze względów perf). Wymyśliłem to, ale nie jestem zbyt dobry w pisaniu kodu lockfree, więc szukam lepszych rozwiązań.

// 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;
}

Zauważ, że clear() jest tylko dla testów, prawdziwy singleton nie miałby tej funkcji.

Author: Xeo, 2012-07-29

5 answers

C++11 usuwa konieczność ręcznego blokowania. Równoczesne wykonanie będzie czekać, jeśli statyczna zmienna lokalna jest już inicjalizowana.

§6.7 [stmt.dcl] p4

Jeśli control wprowadzi deklarację jednocześnie, podczas gdy zmienna jest inicjalizowana, równoczesne wykonanie będzie czekać na zakończenie inicjalizacji.

Jako takie proste mają static funkcję taką jak:

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

To będzie działać poprawnie w C++11 (o ile kompilator poprawnie implementuje tę część standardu, oczywiście).


Oczywiście, prawdziwa poprawna odpowiedź to do , a nie używać singletonu, kropki .

 125
Author: Xeo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-07 01:51:39

Dla mnie najlepszym sposobem na zaimplementowanie Singletona przy użyciu C++11 jest:

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
Author: GutiMac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-07 01:50:22

Trudno jest odczytać twoje podejście, ponieważ nie używasz kodu zgodnie z przeznaczeniem... oznacza to, że wspólny wzorzec dla Singletona to wywołanie instance(), aby uzyskać pojedynczą instancję ,a następnie jej użycie( również, jeśli naprawdę chcesz Singletona, żaden konstruktor nie powinien być publiczny).

W każdym razie nie sądzę, że Twoje podejście jest bezpieczne, Weź pod uwagę, że dwa wątki próbują zdobyć singleton, pierwszy, który zaktualizuje flagę, będzie jedynym inicjalizującym, ale funkcja initialize zakończy się wcześniej na drugim wątku, a ten wątek może przystąpić do użycia Singletona przed pierwszy wątek dostał się do zakończenia inicjalizacji.

Semantyka twojego initialize jest złamana. Jeśli spróbujesz opisać / document zachowanie funkcji będziesz miał trochę zabawy, a skończy się opisanie implementacji, a nie prostej operacji. Dokumentowanie jest zwykle prostym sposobem na podwójne sprawdzenie projektu / algorytmu: jeśli skończysz opisując jak zamiast co , powinieneś wrócić do projektowania. W szczególności, nie ma gwarancji, że po zakończeniu initialize obiekt został faktycznie zainicjowany(tylko jeśli zwracaną wartością jest true, a czasami jeśli jest to false, ale nie zawsze).

 2
Author: David Rodríguez - dribeas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-29 20:07:13

IMHO, najlepszym sposobem na zaimplementowanie singletonów jest użycie wzorca" double-check, single-lock", który można zaimplementować w C++ 11: Double-Checked Locking Is Fixed In C++11 Ten wzorzec jest szybki w już utworzonym przypadku, wymagając tylko jednego porównania wskaźników, i bezpieczny w przypadku pierwszego użycia.

Jak wspomniano w poprzedniej odpowiedzi, C++ 11 gwarantuje construction-order safety dla statycznych zmiennych lokalnych jest lokalny statyczny wątek inicjalizacji zmiennej-safe w C++11?Więc jesteś bezpieczny używając tego wzoru. Jednak Visual Studio 2013 jeszcze go nie obsługuje: - (Zobacz wiersz "magic statics" na tej stronie, więc jeśli używasz VS2013, nadal musisz to zrobić sam.

Niestety nic nie jest proste. Przykładowy kod odwołujący się do powyższego wzorca nie może być wywołany z inicjalizacji CRT, ponieważ statyczna STD:: mutex ma konstruktor, a zatem nie jest gwarantowana inicjalizacja przed pierwszym wywołaniem, aby uzyskać singleton, jeśli to wywołanie jest efektem ubocznym inicjalizacji CRT. Aby obejść to, musisz użyć nie mutex, ale wskaźnik-do-mutex, który jest gwarantowany jako zero-zainicjalizowany przed rozpoczęciem inicjalizacji CRT. Następnie będziesz musiał użyć STD:: atomic:: compare_exchange_strong, aby utworzyć i używać mutex.

Zakładam, że semantyka C++ 11 thread-safe local-static-initialization działa nawet wtedy, gdy jest wywoływana podczas inicjalizacji CRT.

Więc jeśli masz C++ 11 thread-safe local-static-dostępna semantyka inicjalizacji, użyj ich. Jeśli nie, masz trochę pracy do zrobienia, nawet bardziej, jeśli chcesz, aby singleton był bezpieczny dla wątków podczas inicjalizacji CRT.

 2
Author: Wheezil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
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
Author: Pooja Kuntal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-15 09:32:33