Двойной контрольный шаблон блокировки в С++ 11?
Новая машинная модель С++ 11 позволяет многопроцессорным системам работать надежно, прим. к реорганизации инструкций.
Как отметил Мейерс и Александреску, "простая" реализация Double-Checked Locking Pattern небезопасна в С++ 03
Singleton* Singleton::instance() {
if (pInstance == 0) { // 1st test
Lock lock;
if (pInstance == 0) { // 2nd test
pInstance = new Singleton;
}
}
return pInstance;
}
Они показали в свою статью, что независимо от того, что вы делаете в качестве программиста, на С++ 03 у компилятора слишком много свободы: разрешено изменять порядок инструкций таким образом, чтобы вы могли не быть уверены, что в итоге вы получите только один экземпляр Singleton
.
Мой вопрос:
- Ограничения/определения новой модели машины С++ 11 теперь ограничивают последовательность инструкций, что вышеуказанный код всегда будет работать с компилятором С++ 11?
- Как выглядит безопасная С++ 11-реализация этого шаблона Singleton при использовании новых возможностей библиотеки (вместо mock
Lock
здесь)?
Ответы
Ответ 1
Если pInstance
является регулярным указателем, у кода есть потенциальная гонка данных - операции с указателями (или любым встроенным типом, если на то пошло) не гарантируются как атомарные (EDIT: или упорядоченные)
Если pInstance
является std::atomic<Singleton*>
и Lock
внутренне использует std::mutex
для достижения синхронизации (например, если Lock
на самом деле std::lock_guard<std::mutex>
), код должен быть свободным от расы данных.
Обратите внимание, что для обеспечения правильной синхронизации вам нужна и явная блокировка и атомный pInstance
.
Ответ 2
Так как статическая инициализация переменных теперь является потокобезопасной, одноэлементный Meyer должен быть потокобезопасным.
Singleton* Singleton::instance() {
static Singleton _instance;
return &_instance;
}
Теперь вам нужно решить основную проблему: в коде есть синглтон.
EDIT: на основе моего комментария ниже: эта реализация имеет большой недостаток по сравнению с другими. Что произойдет, если компилятор не поддерживает эту функцию? Компилятор выплюнет небезопасный код, даже не выдав предупреждение. Другие решения с замками даже не будут компилироваться, если компилятор не поддерживает новые интерфейсы. Это может быть хорошей причиной не полагаться на эту функцию даже для вещей, отличных от одиночных.
Ответ 3
С++ 11 не изменяет значения этой реализации блокировки с двойной проверкой. Если вы хотите выполнить двойную проверку блокировки, вам необходимо установить соответствующие барьеры/ограждения памяти.