Реализована ли DCL (блокировка с двойной проверкой) в следующем коде C ++ потокобезопасным? - PullRequest
0 голосов
/ 08 мая 2020

Вот фрагмент кода, который представляет собой DCL (блокировку с двойной проверкой), реализованную семантикой «получение-выпуск» в C ++. Код выглядит следующим образом:

std :: atomic <Singleton *> Singleton :: m_instance;
std :: mutex Singleton :: m_mutex;

Singleton * Singleton :: getInstance () {
    Singleton * tmp = m_instance.load (std :: memory_order_acquire); // 3
    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; // 1
            m_instance.store (tmp, std :: memory_order_release); // 2
        }
    }
    return tmp;
}

На https://en.cppreference.com/w/cpp/atomic/memory_order интерпретация memory_order_release следующая: операция сохранения с этим порядком памяти выполняет операцию освобождения: нет чтения или записи в текущий поток может быть переупорядочен после этого хранилища. Все записи в текущем потоке видны в других потоках, которые получают ту же самую переменную atomi c.

Насколько я понимаю: load-store, store-store нельзя переупорядочить, но я не сказал, что другое - хранение не может быть переупорядочено.

Итак, я думаю: «1» включает в себя не только инструкции чтения и записи, но и инструкцию вызова, тогда инструкция вызова может быть переупорядочена после «2»; тогда «3» может получить небезопасный указатель «tmp».

Позвольте мне снова описать предыдущий абзац:

Disassemble ‘1’ into the following two possible pseudo-instructions:
tmp = allocate ();
call Singleton constructor (tmp); // 4

Я думаю, что «4» можно переупорядочить после «2». После того, как один поток выполняет «2», другой поток завершает «3» и получает указатель tmp. В настоящее время указатель tmp является небезопасным указателем Singleton.

Итак, у меня есть вопрос: является ли приведенный выше код потокобезопасным?

1 Ответ

2 голосов
/ 08 мая 2020

Да, это безопасно!

Если Acquire-load возвращает null (т. Е. Синглтон еще не инициализирован), вы получаете мьютекс. Внутри мьютекса перезагрузка может быть ослаблена, поскольку модификации m_instance в любом случае защищены мьютексом, т. Е. Если какой-то другой поток уже инициализировал синглтон, то мьютекс-релиз этого потока должен произойти до того, как наш мьютекс-захват операции, поэтому гарантируется, что мы увидим обновленный m_instance.

Если Acquire-load (1) «видит» значение, записанное хранилищем Release (2), две операции синхронизируются с друг с другом, тем самым устанавливая связь «происходит раньше», так что вы можете безопасно получить доступ к объекту, на который указывает tmp.

Обновление
Release-store также защищен мьютексом, и он не возможно, что часть инициализации tmp переупорядочена с хранилищем. В общем, не следует спорить о возможных переупорядочениях. Стандарт не говорит ничего о том, можно ли / как можно переупорядочить операции. Вместо этого он определяет отношение (между потоками) -случалось-до. Любые переупорядочения, которые может выполнить компилятор, являются просто результатом применения правил отношений «происходит до».

Если приобретение-загрузка (1) загружает значение, записанное хранилищем выпуска (2), две операции синхронизируются друг с другом, тем самым устанавливая связь «происходит до», т. е. (2) происходит до (3). Но поскольку (1) упорядочивается до (2), а отношение «произошло до» транзитивно, необходимо гарантировать, что (1) произойдет до (3). Таким образом, переупорядочить (1) (или его части) с помощью (2) невозможно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...