Чи Singleton
безпечна наступна реалізація з використанням ледачої ініціалізації потоку (Меєрса Синглтон)?
static Singleton& instance()
{
static Singleton s;
return s;
}
Якщо ні, то чому і як зробити його безпечним?
Чи 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
. Щоб бути безпечним для потоків, це повинно відбуватися в критичному розділі, але в стандарті немає вимоги приймати критичний розділ (стандарт на сьогоднішній день повністю замовчується в потоках). Компілятори часто реалізують це за допомогою простої перевірки та збільшення статичного булевого, але не в критичному розділі. Щось на зразок наступного псевдокоду:
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)));
}
Отож ось простий для потоків безпечний Singleton (для 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, щоб забезпечити незахищену платформу для обгортки.main()
- якщо ви викликаєте його до цього часу (як, наприклад, при ініціалізації статичного об'єкта), речі можуть не працювати, оскільки CRITICAL_SECTION
вони не можуть бути ініціалізовані.new Singleton()
кидає?
new Singleton()
кидки, напевно проблема з блокуванням. Слід використовувати належний клас блокування RAII, щось на зразок lock_guard
Boost. Я хотів, щоб приклад був більш-менш автономним, і це вже було трохи чудовисько, тому я відмовився від безпеки виключень (але зате назвав це). Можливо, я мушу це виправити, щоб цей код не ставав вирізаним-не-вставленим десь недоречним.
Переглядаючи наступний стандарт (розділ 6.7.4), він пояснює, наскільки статична локальна ініціалізація є безпечною для потоків. Тож як тільки цей розділ стандарту буде широко впроваджений, Сінглтон Меєра стане кращою реалізацією.
Я вже не згоден з багатьма відповідями. Більшість компіляторів вже реалізують статичну ініціалізацію таким чином. Єдиним помітним винятком є Microsoft Visual Studio.
Чи безпечна низка наступних потоків [...]?
На більшості платформ це не є безпечним для потоків. (Додайте звичайну відмову від відповідальності, пояснюючи, що стандарт C ++ не знає про потоки, тому, юридично, він не говорить про те, є він чи ні.)
Якщо ні, то чому [...]?
Причина цього не в тому, що ніщо не заважає одночасно виконати s
конструктор більш ніж одному потоку .
як зробити цю нитку безпечною?
"C ++ і небезпека подвійного перевірки блокування" Скотта Мейєрса та Андрея Олександреску є досить хорошим трактатом на тему безпечних ниток однотонних.
Як сказав MSalters: Це залежить від реалізації C ++, яку ви використовуєте. Перевірте документацію. Щодо іншого питання: "Якщо ні, то чому?" - Стандарт C ++ ще не згадує нічого про потоки. Але майбутня версія C ++ обізнана з потоками, і в ній прямо вказано, що ініціалізація статичних локальних пристроїв є безпечною для потоків. Якщо дві нитки викликають таку функцію, один потік виконає ініціалізацію, а інший заблокує і чекатиме її завершення.