Безопасна ли реализация Мейерсом потока шаблона Singleton? - PullRequest
122 голосов
/ 02 ноября 2009

Безопасна ли в потоке следующая реализация с использованием отложенной инициализации Singleton (Meyers 'Singleton)?

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

Если нет, то почему и как сделать потокобезопасным?

Ответы [ 6 ]

145 голосов
/ 02 ноября 2009

В C ++ 11 , потокобезопасен. Согласно стандарту , §6.7 [stmt.dcl] p4:

Если контроль входит объявление одновременно во время инициализации переменной, одновременное выполнение должно ожидать завершения инициализации.

Поддержка GCC и VS для этой функции ( Динамическая инициализация и разрушение с параллелизмом , также известная как Магическая статика в MSDN ) выглядит следующим образом:

Спасибо @Mankarse и @olen_gam за их комментарии.

<ч />

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

19 голосов
/ 02 ноября 2009

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

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

Итак, вот простой потокобезопасный синглтон (для Windows). Он использует простую оболочку класса для объекта Windows CRITICAL_SECTION, так что мы можем заставить компилятор автоматически инициализировать CRITICAL_SECTION перед вызовом main(). В идеале должен использоваться настоящий класс критической секции RAII, который может иметь дело с исключениями, которые могут возникнуть при удержании критической секции, но это выходит за рамки этого ответа.

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

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

Человек - это много дерьма, чтобы "улучшить глобальность".

Основные недостатки этой реализации (если я не позволил некоторым ошибкам проскользнуть):

  • , если new Singleton() сработает, блокировка не будет снята. Это можно исправить с помощью настоящего объекта блокировки RAII вместо простого, который я здесь имею. Это также может помочь сделать вещи переносимыми, если вы используете что-то вроде Boost для предоставления независимой от платформы оболочки для блокировки.
  • это гарантирует безопасность потока, когда экземпляр Singleton запрашивается после вызова main() - если вы вызываете его раньше (как при инициализации статического объекта), вещи могут не работать, потому что CRITICAL_SECTION может не инициализироваться.
  • блокировка должна выполняться каждый раз, когда запрашивается экземпляр. Как я уже сказал, это простая потокобезопасная реализация. Если вам нужен лучший (или вы хотите знать, почему такие вещи, как техника двойной проверки блокировки, неверны), см. документы, на которые есть ссылка в ответе Гроо .
9 голосов
/ 02 ноября 2009

Глядя на следующий стандарт (раздел 6.7.4), он объясняет, насколько статическая локальная инициализация является поточно-ориентированной. Так что, как только этот раздел стандарта будет широко внедрен, предпочтение отдается Мейеру Синглтону.

Я уже не согласен со многими ответами. Большинство компиляторов уже реализуют статическую инициализацию таким образом. Единственное заметное исключение - Microsoft Visual Studio.

6 голосов
/ 02 ноября 2009

Правильный ответ зависит от вашего компилятора. Он может решить сделать потокобезопасным; это не "естественно" потокобезопасно.

5 голосов
/ 02 ноября 2009

Является ли следующая реализация [...] поточно-безопасной?

На большинстве платформ это не является потокобезопасным. (Добавьте обычный отказ от ответственности, пояснив, что стандарт C ++ не знает о потоках, поэтому по закону он не говорит, является ли он или нет.)

Если нет, то почему [...]?

Причина этого не в том, что ничто не мешает более чем одному потоку одновременно выполнять конструктор s '.

как сделать потокобезопасным?

"C ++ и опасности двойной проверки блокировки" Скотта Мейерса и Андрея Александреску - довольно хороший трактат на тему поточно-ориентированных синглетонов.

2 голосов
/ 02 ноября 2009

Как сказал MSalters: это зависит от используемой вами реализации C ++. Проверьте документацию. Что касается другого вопроса: «Если нет, то почему?» - Стандарт C ++ пока ничего не упоминает о потоках. Но будущая версия C ++ знает о потоках и явно заявляет, что инициализация статических локальных объектов является потоко-безопасной Если два потока вызывают такую ​​функцию, один поток выполнит инициализацию, а другой заблокирует и дождется ее завершения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...