Служит ли std:: atomic access барьером памяти?

может ли компилятор переупорядочить инструкции по атомике или атомика служит барьером памяти? Опять же, могут ли инструкции, написанные после атомной инструкции, выполняться перед атомной инструкцией?

следующий код. Если useMapA = false перенесен до mapB обновляется и начинается чтение потока, мы будем использовать недопустимый mapB.

Примечание: поток обновления происходит только раз в 15 минут, поэтому у нас очень хорошо структурированный поток и способ избежать используя дорогой вызов блокировки!

std::atomic<bool> useMapA;
std::map<string, string> mapA, mapB;

public void updateMap(map<string, string>* latestMap) {
    if (useMapA) {
        mapB = std::move(*latestMap);
        useMapA = false;
    } else {
        mapA = std::move(*latestMap);
        useMapA = true;
    }
}

inline map<string, string>& getMap() {
    return useMapA ? mapA : mapB;
}

Edit: я заинтересован в торговле на 100% потокобезопасной для скорости (время = деньги). Эта функция чтения должна работать очень быстро. Вы можете предположить, что 15 минут достаточно долго, чтобы избежать условий гонки, которые были бы вызваны, если бы это время было намного короче.

1 ответов


прежде чем ответить на ваш вопрос, я хотел бы показать, как можно легко реализовать функцию с std:: shared_ptr и атомарные операции. Следующая реализация является эффективной и потокобезопасной. Также нет необходимости в читателей для создания копии карты.

using string_map = std::map<std::string, std::string>;

std::shared_ptr<const string_map> map;

std::shared_ptr<const string_map> getMap()
{
    return std::atomic_load(&map);
}

void updateMap(string_map latestMap)
{
    std::shared_ptr<const string_map> temp{
        new string_map{std::move(latestMap)}};
    std::atomic_store(&map, temp);
}

теперь давайте посмотрим на ваш код. Это немного сложнее. Чтобы сделать это проще, предположим, что updateMap вызывается каждый во-вторых, вместо того, чтобы каждые 15 минут. useMapA изначально правда. Поток обновления выполняет следующие инструкции и будет прерван перед обновлением атомарного флага:

if (useMapA) {
    mapB = std::move(*latestMap);

теперь поток чтения оценивает только атомарный флаг:

bool r1 = useMapA; // r1==true

поток обновления продолжается и устанавливает атомарный флаг false. Секунду спустя поток обновления оценивает атомарный флаг:

if (useMapA) { // condition is false

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

что изменится, если updateMap вызывается только каждые 15 минут? Если в течение этих 15 минут не произойдет дополнительной синхронизации, это все равно гонка данных, потому что Стандарт C++ не имеет значения между секундой и 15 минутами.