Czy poniższa implementacja Singleton
wątku (Meyers 'Singleton) przy użyciu leniwej inicjalizacji jest bezpieczna?
static Singleton& instance()
{
static Singleton s;
return s;
}
Jeśli nie, dlaczego i jak zabezpieczyć wątek?
Czy poniższa implementacja Singleton
wątku (Meyers 'Singleton) przy użyciu leniwej inicjalizacji jest bezpieczna?
static Singleton& instance()
{
static Singleton s;
return s;
}
Jeśli nie, dlaczego i jak zabezpieczyć wątek?
Odpowiedzi:
W C ++ 11 jest bezpieczny wątkowo. Zgodnie z normą , §6.7 [stmt.dcl] p4
:
Jeśli sterowanie wprowadzi deklarację jednocześnie podczas inicjalizacji zmiennej, równoczesne wykonanie będzie czekało na zakończenie inicjalizacji.
Obsługa GCC i VS dla tej funkcji ( Dynamic Initialization and Destruction with Concurrency , znana również jako Magic Statics w MSDN ) jest następująca:
Dzięki @Mankarse i @olen_gam za ich komentarze.
W C ++ 03 ten kod nie był bezpieczny dla wątków. Jest artykuł Meyersa zatytułowany „C ++ and the Perils of Double-Checked Locking”, który omawia bezpieczne wątkowo implementacje wzorca, a wniosek jest mniej więcej taki, że (w C ++ 03) pełne blokowanie wokół metody tworzenia instancji jest w zasadzie najprostszym sposobem zapewnienia właściwej współbieżności na wszystkich platformach, podczas gdy większość form podwójnie sprawdzonych wariantów wzorca blokowania może cierpieć z powodu warunków wyścigu na niektórych architekturach , chyba że instrukcje są przeplatane strategicznie rozmieszczonymi barierami pamięci.
Odpowiadając na pytanie, dlaczego nie jest to wątkowo bezpieczne, nie dzieje się tak dlatego, że pierwsze wywołanie instance()
musi wywołać konstruktora Singleton s
. Aby zapewnić bezpieczeństwo wątków, musiałoby to nastąpić w sekcji krytycznej, ale standard nie wymaga, aby została wybrana sekcja krytyczna (dotychczasowy standard całkowicie milczy na temat wątków). Kompilatory często implementują to za pomocą prostego sprawdzenia i inkrementacji statycznej wartości logicznej - ale nie w krytycznej sekcji. Coś w rodzaju następującego pseudokodu:
static Singleton& instance()
{
static bool initialized = false;
static char s[sizeof( Singleton)];
if (!initialized) {
initialized = true;
new( &s) Singleton(); // call placement new on s to construct it
}
return (*(reinterpret_cast<Singleton*>( &s)));
}
Oto prosty, bezpieczny wątkowo Singleton (dla Windows). Używa prostego opakowania klasy dla obiektu CRITICAL_SECTION systemu Windows, dzięki czemu kompilator może automatycznie zainicjować CRITICAL_SECTION
poprzednią main()
wywołanie. Idealnie byłoby użyć prawdziwej klasy sekcji krytycznej RAII, która może obsługiwać wyjątki, które mogą wystąpić, gdy sekcja krytyczna jest utrzymywana, ale to wykracza poza zakres tej odpowiedzi.
Podstawową operacją jest to, że gdy Singleton
żądane jest wystąpienie of , zostaje przyjęta blokada, tworzony jest singleton, jeśli to konieczne, a następnie blokada jest zwalniana i zwracane jest odniesienie do singletona.
#include <windows.h>
class CritSection : public CRITICAL_SECTION
{
public:
CritSection() {
InitializeCriticalSection( this);
}
~CritSection() {
DeleteCriticalSection( this);
}
private:
// disable copy and assignment of CritSection
CritSection( CritSection const&);
CritSection& operator=( CritSection const&);
};
class Singleton
{
public:
static Singleton& instance();
private:
// don't allow public construct/destruct
Singleton();
~Singleton();
// disable copy & assignment
Singleton( Singleton const&);
Singleton& operator=( Singleton const&);
static CritSection instance_lock;
};
CritSection Singleton::instance_lock; // definition for Singleton's lock
// it's initialized before main() is called
Singleton::Singleton()
{
}
Singleton& Singleton::instance()
{
// check to see if we need to create the Singleton
EnterCriticalSection( &instance_lock);
static Singleton s;
LeaveCriticalSection( &instance_lock);
return s;
}
Człowieku - to dużo bzdur, żeby „uczynić świat lepszym”.
Główne wady tej implementacji (jeśli nie przepuściłem kilku błędów) to:
new Singleton()
rzucony, blokada nie zostanie zwolniona. Można to naprawić, używając prawdziwego obiektu blokady RAII zamiast prostego, który mam tutaj. Może to również pomóc w uczynieniu rzeczy przenośnymi, jeśli używasz czegoś takiego jak Boost, aby zapewnić niezależne od platformy opakowanie blokady.main()
wywołaniu - jeśli wywołasz ją wcześniej (jak w przypadku inicjalizacji obiektu statycznego), rzeczy mogą nie działać, ponieważ CRITICAL_SECTION
może nie zostać zainicjowany.new Singleton()
rzuci?
new Singleton()
rzuca, zdecydowanie występuje problem z blokadą. Należy użyć odpowiedniej klasy blokady RAII, coś w rodzaju lock_guard
Boost. Chciałem, żeby przykład był mniej lub bardziej samodzielny, a był już trochę potworem, więc odrzuciłem bezpieczeństwo wyjątków (ale je odwołałem). Może powinienem to naprawić, aby ten kod nie został wycięty i wklejony w niewłaściwym miejscu.
Patrząc na następny standard (sekcja 6.7.4), wyjaśnia, jak statyczna inicjalizacja lokalna jest bezpieczna dla wątków. Kiedy więc ta sekcja standardu zostanie szeroko zaimplementowana, Meyer's Singleton będzie preferowaną implementacją.
Nie zgadzam się już z wieloma odpowiedziami. Większość kompilatorów już implementuje statyczną inicjalizację w ten sposób. Jedynym godnym uwagi wyjątkiem jest Microsoft Visual Studio.
Czy następujący wątek [...] implementacji jest bezpieczny?
Na większości platform nie jest to bezpieczne dla wątków. (Dołącz zwykłe zastrzeżenie wyjaśniające, że standard C ++ nie wie o wątkach, więc zgodnie z prawem nie mówi, czy tak jest, czy nie).
Jeśli nie, to dlaczego […]?
Przyczyną tego nie jest to, że nic nie stoi na przeszkodzie, aby więcej niż jeden wątek wykonywał jednocześnie s
konstruktora.
jak zabezpieczyć wątek?
„C ++ and the Perils of Double-Checked Locking” Scotta Meyersa i Andrei Alexandrescu to całkiem niezły traktat na temat singletonów bezpiecznych dla wątków.
Jak powiedział MSalters: To zależy od używanej implementacji C ++. Sprawdź dokumentację. A jeśli chodzi o drugie pytanie: „Jeśli nie, to dlaczego?” - Standard C ++ nie wspomina jeszcze nic o wątkach. Jednak nadchodząca wersja C ++ jest świadoma wątków i wyraźnie stwierdza, że inicjalizacja statycznych lokalizacji lokalnych jest bezpieczna dla wątków. Jeśli dwa wątki wywołują taką funkcję, jeden wątek wykona inicjalizację, podczas gdy drugi będzie blokował i czekał na zakończenie.