Правильна ли эта реализация шаблона двойной проверки блокировки (DCLP) в C ++ 11?

Я читаю о DCLP (дважды проверенная схема блокировки), и я не уверен, что понял правильно. При использовании атомики для создания блокировки (как описано в DCLP исправлено в C ++ 11), и есть 2 вещи, которые не ясны:

  1. В коде из статьи:
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton;
m_instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}

Что произойдет, если я найду забор внутри «load ()», но чем tmp не будет nullptr, и я просто вернусь? Разве мы не должны указывать, где процессор может «снять ограждение»?

И если нет необходимости снимать ограждение, то почему мы должны приобретать и освобождать? в чем разница?

Ужасно мне не хватает чего-то простого ….

  1. Если я правильно понял статью, это правильный путь для реализации DCLP?
Singleton* Singleton::m_instance = null;
std::atomic<bool> Singleton::is_first; // init to false
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
bool tmp = is_first.load(std::memory_order_acquire);
if (tmp == false) {
std::lock_guard<std::mutex> lock(m_mutex);
tmp = is_first.load(std::memory_order_relaxed);
if (tmp == false) {
// can place any code that will run exactly once!
m_instance = new Singleton;

// store back the tmp atomically
is_first.store(tmp, std::memory_order_release);
}
}
return m_instance;
}

Другими словами, вместо того, чтобы смотреть на экземпляр, я использую атомарное логическое значение, чтобы убедиться, что DCLP работает, и все, что находится внутри второго tmp, должно быть синхронизировано и выполнено один раз. Это правильно?

Спасибо!

РЕДАКТИРОВАТЬ: Обратите внимание, я не задаю вопрос для реализации синглтон, но просто, чтобы лучше понять концепции заборов и атомных и как это исправлено DCLP. Это теоретический вопрос.

1

Решение

Что произойдет, если я найду забор внутри «load ()», но чем tmp не будет nullptr, и я просто вернусь? Разве мы не должны указывать, где процессор может «снять ограждение»?

Нет, релиз делается, когда магазин в m_instance случается. Если вы загружаете m_instance и это не ноль, тогда релиз уже произошел ранее, и вам не нужно это делать.

Вы не «приобретаете забор» и «освобождаете забор», как если бы вы приобрели мьютексный замок. Это не то, что заборы. Забор — это просто операция получения или освобождения без соответствующей ячейки памяти. И заборы здесь не очень актуальны, потому что все операции получения и выпуска имеют связанную область памяти (атомарный объект m_instance).

Вам не нужно приобретать + отпускать в согласованных парах, таких как блокировки мьютекса + разблокировки. Вы можете сделать одну операцию освобождения, чтобы сохранить значение и иметь любое количество операций получения (ноль или более), которые загружают это значение и наблюдают за его эффектами.

Семантика получения / выпуска в загрузках / хранилищах связана с упорядочением операций на любой стороне загрузки / хранилища, чтобы предотвратить повторный заказ.

Непринужденное атомарное хранилище (то есть операция освобождения) для переменной A будет синхронизировать с более поздняя неослабленная атомная нагрузка (то есть операция получения) той же переменной A.

Как говорит стандарт C ++:

Неофициально, выполняя операцию разъединения на стороне А, предшествующей
влияние на другие области памяти, чтобы стать видимым для других потоков, которые позже выполняют операцию получения или получения на A.

Итак, в приведенном вами коде DCLP m_instance.store(tmp, memory_order_release) это магазин для m_instance и является операцией освобождения. m_instance.load(memory_order_acquire) нагрузка от m_instance и является операцией приобретения. Модель памяти говорит, что хранилище ненулевого указателя синхронизируется с любой загрузкой, которая видит ненулевой указатель, что означает, что гарантируется, что все эффекты new Singleton завершено, прежде чем любой поток может загрузить ненулевое значение из tmp, Это устраняет проблемы, связанные с двойной проверкой блокировки до C ++ 11, когда хранилище tmp может стать видимым для других потоков, прежде чем объект будет полностью построен.

Другими словами, вместо того, чтобы смотреть на экземпляр, я использую атомарное логическое значение, чтобы убедиться, что DCLP работает, и все, что находится внутри второго tmp, должно быть синхронизировано и выполнено один раз. Это правильно?

Нет, потому что вы храните false Вот:

        // store back the tmp atomically
is_first.store(tmp, std::memory_order_release);

Что означает при следующем вызове функции вы создаете другую Singleton и утечка первого. Так должно быть:

        is_first.store(true, std::memory_order_release);

Если вы исправите это, я думаю, что это правильно, но в типичных реализациях он использует больше памяти (sizeof(atomic<bool>)+sizeof(Singleton*) вероятно, больше, чем sizeof(atomic<Singleton*>)), и, разбив логику на две переменные (логическое значение и указатель), вы упростите ошибку, как и раньше. Таким образом, нет никакого преимущества в этом способе по сравнению с оригиналом, где сам указатель также служит логическим значением, потому что вы смотрите непосредственно на указатель, а не на какое-то логическое значение, которое могло быть установлено неправильно.

3

Другие решения


По вопросам рекламы ammmcru@yandex.ru
Adblock
detector