Является ли DCL (блокировка с двойной проверкой) поточно-ориентированной, если она реализована семантикой «Последовательно согласованная атомика» в C ++? - PullRequest
2 голосов
/ 08 мая 2020

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

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

Singleton * Singleton :: getInstance () {
     Singleton * tmp = m_instance.load (); // 3
     if (tmp == nullptr) {
         std :: lock_guard <std :: mutex> lock (m_mutex);
         tmp = m_instance.load ();
         if (tmp == nullptr) {
             tmp = new Singleton; // 1
             m_instance.store (tmp); // 2
         }
     }
     return tmp;
}

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

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

Ответы [ 4 ]

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

Примечание: я знаю, что этот ответ верен для C и C ++. Это не обязательно верно для других языков, и я не знаю, применимо ли это к Java.

Компилятор в основном свободен делать все, что не меняет внешнего поведения программы, так что да, они могут быть переупорядочены.

Думаю, вопрос немного философский. Необязательно, чтобы вы могли сказать, что определенная инструкция сборки принадлежит определенной строке в коде C. Часто это так, но часто нет, и если вы включите оптимизацию, она может немного изменить ситуацию.

В каком-то смысле вопрос бессмысленный, потому что, когда программа выполняется, она не выполняется line по строке. Одно исключение - если вы скомпилируете его с параметром отладки. Это позволит вам выполнять программу построчно. В этом случае они не будут переупорядочены.

Обратите внимание, что как только вы вызываете неопределенное поведение, компилятор может делать то, что он хочет. И да, иногда строка, вызывающая неопределенное поведение, вызывает довольно странную реорганизацию кода. Например, эта программа:

int main()
{
    int a;
    printf("Hello, World!\n");
    a=1/0; // This causes UB
}

не гарантирует вывод «Hello, World!», Даже если строка, вызывающая UB, идет после печати. ​​

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

Да, инструкции могут быть переупорядочены вне if или while. Но только в том случае, если это не приведет к изменению определенного поведения программы.

Например, вычисление, такое как добавление, может быть выполнено вне if, но его результат сохраняется только внутри него.

Стоит отметить, что если у вас есть неопределенное поведение в вашей программе, это может вызвать чрезвычайно неожиданное поведение. В том числе, чтобы эффект неопределенного поведения произошел до его причины. https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633 содержит несколько хороших примеров преобразований, которые современные компиляторы могут выполнять с неопределенным поведением.

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

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

In Реализована ли DCL (блокировка с двойной проверкой) в следующем коде C ++, ориентированном на потоки? Я уже объяснил, почему. Здесь вы использовали Acquire / Release вместо seq-cst, но аргумент остался прежним.

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

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

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

1 голос
/ 08 мая 2020

Да ...

При написании кода вы пишете не инструкции для ЦП, а абстрактное описание, которое компилятор использует для создания исполняемого файла. Компиляторы могут переупорядочивать "инструкции", когда они видят это подходящим, и когда это не меняет наблюдаемое поведение программы, как определено соответствующей спецификацией языка.

... и нет

Когда При написании кода на самом деле не имеет большого значения, что компилятор применяет к нему преобразования, потому что эти преобразования не изменят наблюдаемое поведение программы. Полученная программа будет вести себя так, как если бы инструкции были выполнены в том порядке, в котором вы их записали в своем коде.

...