Могут ли изменчивые, но незащищенные чтения давать бесконечно устаревшие значения? (на реальном оборудовании)

Отвечаяэтот вопрос возник еще один вопрос о ситуации ОП, в котором я не был уверен: это в основном вопрос архитектуры процессоров, но также и вопрос о модели памяти C ++ 11.

По сути, код OP зацикливался на более высоких уровнях оптимизации из-за следующего кода (слегка измененного для простоты):

while (true) {
    uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
    if (ov & MASK) {
        continue;
    }
    if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
        break;
    }
}

где__sync_val_compare_and_swap() встроенный в GCC атомарный CAS. GCC (разумно) оптимизировал это в бесконечный цикл в случае, еслиbits_ & mask было обнаружено, чтоtrue перед входом в цикл полностью пропустите операцию CAS, поэтому я предложил следующее изменение (которое работает):

while (true) {
    uint8_t ov = bits_; // bits_ is some "uint8_t" non-local variable
    if (ov & MASK) {
        __sync_synchronize();
        continue;
    }
    if (ov == __sync_val_compare_and_swap(&bits_, ov, ov | MASK)) {
        break;
    }
}

После того как я ответил, OP отметил, что изменениеbits_ вvolatile uint8_t кажется, работает также. Я предложил не идти по этому пути, так какvolatile обычно не следует использовать для синхронизации, и, похоже, в любом случае здесь не так уж много недостатков.

Тем не менее, я думал об этом больше, и в этом случае семантика такова, что это не имеет значения, еслиov & MASK проверка основана на устаревшем значении, если оно не основано на неопределенно устаревшем значении (то есть, пока цикл в конечном итоге прерывается), так как фактическая попытка обновленияbits_ синхронизирован. Так этоvolatile здесь достаточно, чтобы гарантировать, что этот цикл в конце концов завершится, еслиbits_ обновляется другим потоком, так чтоbits_ & MASK == falseдля любого существующего процессора? Другими словами, при отсутствии явного ограничения памяти, возможно ли практически, чтобы операции чтения, не оптимизированные компилятором, эффективно оптимизировались процессором, на неопределенный срок? (РЕДАКТИРОВАТЬ: Чтобы быть ясным, я спрашиваю здесь о том, что на самом деле может делать современное оборудование, учитывая предположение, что чтения передаются в цикле компилятором, так что это технически не вопрос языка, хотя его выражение в терминах семантики C ++ удобно.)

Это аппаратный аспект, но чтобы немного его обновить и сделать также ответным вопросом о модели памяти C ++ 11, рассмотрим следующий вариант кода выше:

// bits_ is "std::atomic<unsigned char>"
unsigned char ov = bits_.load(std::memory_order_relaxed);
while (true) {
    if (ov & MASK) {
        ov = bits_.load(std::memory_order_relaxed);
        continue;
    }
    // compare_exchange_weak also updates ov if the exchange fails
    if (bits_.compare_exchange_weak(ov, ov | MASK, std::memory_order_acq_rel)) {
        break;
    }
}

cppreference заявляет, чтоstd::memory_order_relaxed подразумевает «отсутствие ограничений на переупорядочение обращений к памяти вокруг атомарной переменной», поэтому независимо от того, что будет или не будет делать настоящее аппаратное обеспечение, подразумевается, чтоbits_.load(std::memory_order_relaxed) может техническиникогда прочитать обновленное значение послеbits_ обновляется в другом потоке в соответствующей реализации?

РЕДАКТИРОВАТЬ: Я нашел это в стандарте (29,4 стр. 13):

Реализации должны сделать атомные хранилища видимыми для атомных нагрузок в течение разумного периода времени.

Таким образом, очевидно, что ожидание «бесконечно долгого» обновленного значения исключено (главным образом?), Но нет никакой твердой гарантии того, что какой-либо конкретный временной интервал свежести, кроме этого, должен быть «разумным»; Тем не менее, вопрос о реальном поведении оборудования стоит.

Ответы на вопрос(4)

Ваш ответ на вопрос