Singleton
Безопасна ли следующая реализация с использованием отложенной инициализации потока (Meyers 'Singleton)?
static Singleton& instance()
{
static Singleton s;
return s;
}
Если нет, то почему и как сделать это потокобезопасным?
Singleton
Безопасна ли следующая реализация с использованием отложенной инициализации потока (Meyers 'Singleton)?
static Singleton& instance()
{
static Singleton s;
return s;
}
Если нет, то почему и как сделать это потокобезопасным?
Ответы:
В C ++ 11 это потокобезопасно. Согласно стандарту ,
§6.7 [stmt.dcl] p4
:GCC и VS поддерживают эту функцию ( динамическая инициализация и разрушение с параллелизмом , также известная как Magic Statics на MSDN ):
Спасибо @Mankarse и @olen_gam за их комментарии.
В C ++ 03 этот код не был потокобезопасным. Есть статья Мейерса под названием «C ++ и опасности двойной проверки блокировки», в которой обсуждаются поточно-ориентированные реализации шаблона, и делается вывод, что более или менее (в C ++ 03) полная блокировка вокруг экземпляра метода в основном это самый простой способ обеспечения надлежащего параллелизма на всех платформах, в то время как большинство форм проверенных вариантов шаблонов блокировки с двойной проверкой могут страдать от состояния гонки на определенных архитектурах , если инструкции не чередуются со стратегически размещаемыми барьерами памяти.
источник
Чтобы ответить на ваш вопрос о том, почему это не потокобезопасно, это не потому, что первый вызов
instance()
должен вызывать конструкторSingleton s
. Чтобы быть потокобезопасным, это должно происходить в критической секции, но в стандарте нет требования, чтобы критическая секция была принята (стандарт на сегодняшний день полностью ничего не говорит о потоках). Компиляторы часто реализуют это с помощью простой проверки и приращения статического логического значения - но не в критической секции. Что-то вроде следующего псевдокода:Итак, вот простой потокобезопасный синглтон (для Windows). Он использует простую оболочку класса для объекта Windows CRITICAL_SECTION, так что мы можем сделать так, чтобы компилятор автоматически инициализировал вызов
CRITICAL_SECTION
beforemain()
. В идеале должен использоваться настоящий класс критической секции RAII, который может иметь дело с исключениями, которые могут возникнуть при удержании критической секции, но это выходит за рамки этого ответа.Основная операция заключается в том, что когда
Singleton
запрашивается экземпляр , берется блокировка, при необходимости создается Singleton, затем блокировка освобождается и возвращается ссылка Singleton.Человек - это много дерьма, чтобы «сделать мир лучше».
Основными недостатками этой реализации (если я не позволил некоторым ошибкам проскользнуть) является:
new Singleton()
бросает, замок не будет освобожден. Это можно исправить с помощью настоящего объекта блокировки RAII вместо простого, который у меня есть здесь. Это также может помочь сделать вещи переносимыми, если вы используете что-то вроде Boost для предоставления независимой от платформы оболочки для блокировки.main()
вызова - если вы вызываете его до этого (как в случае инициализации статического объекта), вещи могут не работать, потому что ониCRITICAL_SECTION
могут быть не инициализированы.источник
new Singleton()
бросит?new Singleton()
выбрасывает, определенно проблема с блокировкой. Нужно использовать правильный класс блокировки RAII, что-то вродеlock_guard
Boost. Я хотел, чтобы пример был более или менее самодостаточным, и он уже был чем-то вроде монстра, поэтому я остановил исключение безопасности (но вызвал его). Может быть, я должен исправить это, чтобы этот код не вставлялся где-то неподходящим образом.Глядя на следующий стандарт (раздел 6.7.4), он объясняет, насколько статическая локальная инициализация является поточно-ориентированной. Поэтому, как только этот раздел стандарта будет широко внедрен, предпочтение отдается Мейеру Синглтону.
Я не согласен со многими ответами уже. Большинство компиляторов уже реализуют статическую инициализацию таким образом. Единственным заметным исключением является Microsoft Visual Studio.
источник
Правильный ответ зависит от вашего компилятора. Он может решить сделать его потокобезопасным; это не "естественно" потокобезопасно.
источник
На большинстве платформ это не потокобезопасно. (Добавьте обычный отказ от ответственности, пояснив, что стандарт C ++ не знает о потоках, поэтому по закону он не говорит, является ли он или нет.)
Причина этого заключается в том, что ничто не мешает более чем одному потоку одновременно выполнять
s
конструктор.«C ++ и опасности двойной проверки блокировки» Скотта Мейерса и Андрея Александреску - довольно хороший трактат на тему поточно-ориентированных синглетонов.
источник
Как сказал MSalters: это зависит от используемой вами реализации C ++. Проверьте документацию. Что касается другого вопроса: «Если нет, то почему?» - Стандарт C ++ пока ничего не упоминает о потоках. Но будущая версия C ++ знает о потоках и явно заявляет, что инициализация статических локальных объектов является поточно-ориентированной. Если два потока вызывают такую функцию, один поток выполнит инициализацию, а другой заблокирует и дождется ее завершения.
источник