Экземпляр синглтона, объявленный как статическая переменная метода GetInstance, является ли он потокобезопасным?

Я видел реализации шаблонов Singleton, где переменная экземпляра была объявлена ​​как статическая переменная в методе GetInstance. Как это:

SomeBaseClass &SomeClass::GetInstance()
{
   static SomeClass instance;
   return instance;
}

Я вижу следующие положительные стороны этого подхода:

  • Код проще, потому что это компилятор, который отвечает за создание этого объекта только при первом вызове GetInstance.
  • Код безопаснее, потому что нет другого способа получить ссылку на экземпляр, кроме как с помощью метода GetInstance, и нет другого способа изменить экземпляр, кроме как внутри метода GetInstance.

Каковы отрицательные стороны этого подхода (кроме того, что это не очень ООП)? Этот потокобезопасный?

4 ответа

Решение

В C++11 это потокобезопасно:

§6.7 [stmt.dcl] p4 Если элемент управления вводит объявление одновременно, когда переменная инициализируется, параллельное выполнение должно ожидать завершения инициализации.

В C++03:

  • Под g ++ это потокобезопасно.
    Но это потому, что g ++ явно добавляет код, чтобы гарантировать это.

Одна проблема заключается в том, что если у вас есть два синглета, и они пытаются использовать друг друга во время строительства и разрушения.

Прочитайте это: Поиск проблем статического порядка инициализации C++

Вариант этой проблемы - доступ к синглтону из деструктора глобальной переменной. В этой ситуации синглтон определенно был уничтожен, но метод get все равно будет возвращать ссылку на уничтоженный объект.

Есть способы обойти это, но они грязные и не стоит делать. Только не получайте доступ к синглтону из деструктора глобальной переменной.

Более безопасное определение, но безобразное:
Я уверен, что вы можете добавить некоторые соответствующие макросы, чтобы привести в порядок это

SomeBaseClass &SomeClass::GetInstance()
{
#ifdef _WIN32 
Start Critical Section Here
#elif  defined(__GNUC__) && (__GNUC__ > 3)
// You are OK
#else
#error Add Critical Section for your platform
#endif

    static SomeClass instance;

#ifdef _WIN32
END Critical Section Here
#endif 

    return instance;
}

Это не потокобезопасно, как показано на рисунке. Язык C++ ничего не говорит о потоках, поэтому у вас нет никаких гарантий со стороны языка. Вам нужно будет использовать примитивы синхронизации платформы, например Win32::EnterCriticalSection(), для защиты доступа.

Ваш конкретный подход будет проблематичным, потому что компилятор вставит некоторый (не потокобезопасный) код для инициализации статического instance при первом вызове, скорее всего, это произойдет до того, как тело функции начнет выполнение (и, следовательно, до того, как может быть запущена любая синхронизация).

Используя глобальный / статический член указатель на SomeClass и затем инициализация в синхронизированном блоке окажется менее проблематичной для реализации.

#include <boost/shared_ptr.hpp>

namespace
{
  //Could be implemented as private member of SomeClass instead..
  boost::shared_ptr<SomeClass> g_instance;
}

SomeBaseClass &SomeClass::GetInstance()
{
   //Synchronize me e.g. ::EnterCriticalSection()
   if(g_instance == NULL)
     g_instance = boost::shared_ptr<SomeClass>(new SomeClass());
   //Unsynchronize me e.g. :::LeaveCriticalSection();
   return *g_instance;
}

Я не скомпилировал это, так что это только для иллюстрации. Он также использует библиотеку boost для получения того же времени жизни (или около того), что и в вашем исходном примере. Вы также можете использовать std::tr1 (C++0x).

Согласно спецификации это должно работать и в VC++. Кто-нибудь знает, если это так?

Просто добавьте ключевое слово volatile. Затем компилятор Visual C++ должен генерировать мьютексы, если документ на MSDN правильный.

SomeBaseClass &SomeClass::GetInstance()
{
   static volatile SomeClass instance;
   return instance;
}

Он разделяет все распространенные недостатки реализации Singleton, а именно:

  • Это непроверенно
  • Это не потокобезопасно (это достаточно тривиально, чтобы увидеть, если вы представляете, как два потока входят в функцию одновременно)
  • Это утечка памяти

Я рекомендую никогда не использовать Singleton в любом производственном коде.

Другие вопросы по тегам