Двойной контрольный шаблон блокировки в С++ 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 не изменяет значения этой реализации блокировки с двойной проверкой. Если вы хотите выполнить двойную проверку блокировки, вам необходимо установить соответствующие барьеры/ограждения памяти.