Как создать потоковый одноэлементный шаблон в Windows?

Я читал о нитевидных одноэлементных моделях здесь:

http://en.wikipedia.org/wiki/Singleton_pattern#C.2B.2B_.28using_pthreads.29

И он говорит внизу, что единственным безопасным способом является использование pthread_once - который недоступен в Windows.

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

Я прочитал эту тему на SO:

Решить безопасную ленивую конструкцию синглтона на С++

И, кажется, намекает на функцию замены и сравнения уровня атомной ОС, которую я предполагаю в Windows:

http://msdn.microsoft.com/en-us/library/ms683568.aspx

Может ли это делать то, что я хочу?

Изменить: Мне нужна ленивая инициализация, и только там будет только один экземпляр класса.

Кто-то на другом сайте, упомянутом с использованием глобальной внутри пространства имен (и он описал одноэлемент как анти-шаблон) - как это может быть "анти-шаблон"?

Принятый ответ:
Я принял Josh answer, поскольку я использую Visual Studio 2008 - NB: для будущих читателей, если вы не используете этот компилятор (или 2005) - Дон ' t используйте принятый ответ!

Edit: Код работает отлично, за исключением оператора return - я получаю сообщение об ошибке: ошибка C2440: "return": невозможно преобразовать из "volatile Singleton *" в "Singleton *". Должен ли я изменить возвращаемое значение как volatile Singleton *?

Изменить: По-видимому, const_cast < > удалит изменчивый классификатор. Еще раз спасибо Джошу.

Ответы

Ответ 1

Если вы используете Visual С++ 2005/2008, вы можете использовать шаблон с двойной проверкой блокировки, поскольку " изменчивые переменные ведут себя как заграждения". Это самый эффективный способ реализовать ленивый инициализированный синглтон.

Из Журнал MSDN:

Singleton* GetSingleton()
{
    volatile static Singleton* pSingleton = 0;

    if (pSingleton == NULL)
    {
        EnterCriticalSection(&cs);

        if (pSingleton == NULL)
        {
            try
            {
                pSingleton = new Singleton();
            }
            catch (...)
            {
                // Something went wrong.
            }
        }

        LeaveCriticalSection(&cs);
    }

    return const_cast<Singleton*>(pSingleton);
}

Всякий раз, когда вам нужен доступ к синглтону, просто вызовите GetSingleton(). При первом вызове статический указатель будет инициализирован. После его инициализации проверка NULL предотвратит блокировку только для чтения указателя.

НЕ используйте это на любом компиляторе, так как он не переносится. Стандарт не дает никаких гарантий относительно того, как это будет работать. Visual С++ 2005 явно добавляет семантику volatile, чтобы сделать это возможным.

Вам придется объявить и инициализировать CRITICAL SECTION в другом месте кода. Но эта инициализация дешевая, поэтому ленивая инициализация обычно не важна.

Ответ 2

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

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

Ленивая инициализация также может быть достигнута с использованием аналогичного механизма. Обычная проблема, с которой приходится сталкиваться, заключается в том, что мьютекс, необходимый для обеспечения безопасности потоков, часто инициализируется в самом одиночном тоне, который просто подталкивает проблему безопасности потока к инициализации раздела мьютекса/критического состояния. Один из способов преодоления этой проблемы - создать и инициализировать секцию mutex/critical в главном потоке вашего приложения, а затем передать ее в singleton посредством вызова статической функции-члена. Инициализация синглтона в тяжелом состоянии затем может выполняться поточно-безопасным способом с использованием этого предварительно инициализированного раздела мьютекса/критического состояния. Например:

// A critical section guard - create on the stack to provide 
// automatic locking/unlocking even in the face of uncaught exceptions
class Guard {
    private:
        LPCRITICAL_SECTION CriticalSection;

    public:
        Guard(LPCRITICAL_SECTION CS) : CriticalSection(CS) {
            EnterCriticalSection(CriticalSection);
        }

        ~Guard() {
            LeaveCriticalSection(CriticalSection);
        }
};

// A thread-safe singleton
class Singleton {
    private:
        static Singleton* Instance;
        static CRITICAL_SECTION InitLock;
        CRITICIAL_SECTION InstanceLock;

        Singleton() {
            // Time consuming initialization here ...

            InitializeCriticalSection(&InstanceLock);
        }

        ~Singleton() {
            DeleteCriticalSection(&InstanceLock);
        }

    public:
        // Not thread-safe - to be called from the main application thread
        static void Create() {
            InitializeCriticalSection(&InitLock);
            Instance = NULL;
        }

        // Not thread-safe - to be called from the main application thread
        static void Destroy() {
            delete Instance;
            DeleteCriticalSection(&InitLock);
        }

        // Thread-safe lazy initializer
        static Singleton* GetInstance() {
            Guard(&InitLock);

            if (Instance == NULL) {
                Instance = new Singleton;
            }

            return Instance;
        }

        // Thread-safe operation
        void doThreadSafeOperation() {
            Guard(&InstanceLock);

            // Perform thread-safe operation
        }
};

Тем не менее, есть веские причины избегать использования синглтонов (и почему их иногда называют анти-шаблон):

  • Они по сути являются прославленными глобальными переменными.
  • Они могут привести к высокой связи между разрозненными частями приложения.
  • Они могут сделать модульное тестирование более сложным или невозможным (из-за сложного обмена реальными синглтонами с поддельными реализациями).

Альтернативой является использование "логического синглтона", посредством которого вы создаете и инициализируете один экземпляр класса в основном потоке и передаете его объектам, которые его требуют. Этот подход может стать громоздким, когда есть много объектов, которые вы хотите создать как одиночные. В этом случае разрозненные объекты могут быть объединены в один объект "Контекст", который затем передается там, где это необходимо.

Ответ 3

В то время как мне нравится принятое решение, я нашел еще одно многообещающее руководство и подумал, что должен поделиться им здесь: Однократная инициализация (Windows)

Ответ 4

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

Ответ 5

Есть один поясняющий момент, который вам нужно рассмотреть для этого вопроса. Вам нужно...

  • Этот и только один экземпляр класса действительно создан
  • Многие экземпляры класса могут быть созданы, но должен быть только один истинный окончательный экземпляр класса

В Интернете есть много примеров для реализации этих шаблонов на С++. Здесь Пример кода проекта

Ответ 6

Ниже объясняется, как это сделать на С#, но точно такая же концепция применима к любому языку программирования, который поддерживает одноэлементный шаблон

http://www.yoda.arachsys.com/csharp/singleton.html

То, что вам нужно решить, это то, что вы хотите ленивой инициализации или нет. Ленивая инициализация означает, что объект, содержащийся внутри синглтона, создается при первом вызове к нему ex:

MySingleton::getInstance()->doWork();

если этот вызов не будет выполнен до конца, существует опасность состояния гонки между потоками, как описано в статье. Однако, если вы положили

MySingleton::getInstance()->initSingleton();

в самом начале вашего кода, где вы предполагаете, что он будет потокобезопасным, тогда вы уже не ленивы инициализации, вам потребуется "некоторая" больше вычислительной мощности при запуске вашего приложения. Однако, если вы это сделаете, это позволит решить много головных болей.

Ответ 7

Если вы ищете более портативное и простое решение, вы можете включить его.

boost:: call_once можно использовать для инициализации потоковой безопасности.

Его довольно простая в использовании и будет частью следующего стандарта С++ 0x.

Ответ 8

Вопрос не требует, чтобы синглтон был ленивым или нет. Поскольку многие ответы предполагают, что, я предполагаю, что для первой фразы обсудите:

Учитывая тот факт, что сам язык не является осведомленностью о потоках, а также метод оптимизации, написать переносимый надежный синглтон С++ очень сложно (если не невозможно), см. "" С++ "и" Опасности блокировки с двойным проверкой" Скот Мейерс и Андрей Александреску.

Я видел, что многие из ответов касаются объекта синхронизации на платформе Windows с использованием CriticalSection, но CriticalSection является только потокобезопасным, когда все потоки работают на одном процессоре, сегодня это, вероятно, не так.

MSDN cite: "Нити одного процесса могут использовать объект критического раздела для синхронизации взаимного исключения".

И http://msdn.microsoft.com/en-us/library/windows/desktop/ms682530 (v = vs .85).aspx

уточнить его:

Объект критического сечения обеспечивает синхронизацию, аналогичную той, которая предоставляется объектом mutex, за исключением того, что критический раздел может использоваться только потоками одного процесса.

Теперь, если "ленивое построение" не является требованием, следующее решение является как кросс-модульным безопасным, так и потокобезопасным и даже портативным:

struct X { };

X * get_X_Instance()
{
    static X x;
    return &x;
}
extern int X_singleton_helper = (get_X_instance(), 1);

Он кросс-модуль-безопасен, потому что мы используем локальный локализованный объект вместо глобального объекта с пространством имен/пространства имен.

Он потокобезопасен, потому что: X_singleton_helper должен быть присвоен правильному значению перед входом в main или DllMain. Он не лениво-построен также из-за этого факта), в этом выражении запятая является оператором, а не пунктуацией.

Явно использую "extern" здесь, чтобы предотвратить оптимизацию компилятора (проблема в статье Скотта Мейерса, большой враг - оптимизатор.), а также сделать инструмент статического анализа, такой как pc-lint, хранить молчание. "Перед main/DllMain" Скотт Майер назвал "однопоточную часть запуска" в пункте "Эффективный С++ 3rd".

Однако я не очень уверен, разрешен ли компилятору оптимизировать вызов get_X_instance() в соответствии со стандартом языка, прокомментируйте.

Ответ 9

Существует много способов сделать потокобезопасную инициализацию Singleton * для окон. На самом деле некоторые из них даже кросс-платформенные. В потоке SO, к которому вы привязались, они искали Singleton, который лениво построен на C, что немного более специфично и может быть немного сложнее сделать правильно, учитывая тонкости модели памяти, в которой вы работаете.

  • который вы никогда не должны использовать