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

StackOverflow https://stackoverflow.com/questions/164496

  •  03-07-2019
  •  | 
  •  

Вопрос

Я читал о потокобезопасных одноэлементных шаблонах здесь:

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

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

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

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

Потокобезопасная ленивая конструкция синглтона в C++

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

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

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

Редактировать: Я хотел бы ленивую инициализацию и чтобы существовал только один экземпляр класса.

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

Принятый ответ:
Я принял Ответ Джоша поскольку я использую Visual Studio 2008 - NB:Для будущих читателей: если вы не используете этот компилятор (или 2005 год) — не используйте принятый ответ!

Редактировать: Код работает нормально, за исключением оператора возврата - я получаю ошибку:ошибка C2440:'возвращаться' :невозможно преобразовать из «летучего Singleton *» в «Singleton *».Должен ли я изменить возвращаемое значение, чтобы оно было изменчивым Singleton *?

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

Это было полезно?

Решение

Если вы используете Visual C++ 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 C++ 2005 явно добавляет семантику изменчивости.

Вам придется заявить и инициализировать КРИТИЧЕСКИЙ РАЗДЕЛ в другом месте кода.Но эта инициализация обходится дешево, поэтому ленивая инициализация обычно не важна.

Другие советы

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

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

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

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

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

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

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

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

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

В этом вопросе необходимо учитывать один уточняющий момент.Вам требуется...

  1. Фактически создается один и только один экземпляр класса.
  2. Можно создать множество экземпляров класса, но должен быть только один настоящий окончательный экземпляр класса.

В Интернете можно найти множество примеров реализации этих шаблонов на C++.Вот Пример проекта кода

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

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

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

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

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

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

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

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

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

Он довольно прост в использовании и станет частью следующего стандарта C++0x.

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

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

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

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

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

уточните еще:

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

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

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» Скотт Мейер назвал «однопоточную часть запуска» в пункте 4 «Эффективного C++ 3».

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

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

  • который вы никогда не должны использовать
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top