Может ли гонка данных привести к чему-то худшему, чем просто чтение значения мусора?

Стандарты C11 и C ++ 11 определяют, что одновременное неатомарное чтение и запись в одну и ту же ячейку памяти является гонкой данных, которая приводит к UB, поэтому такая программа может делать практически все, что угодно. Хорошо понял. Я хочу понять причину этого слишком (для меня сегодня) строгих требований.

Я играю с еще одним механизмом IPC, использующим этот плохой и быстрый доступ к памяти. Я не буду беспокоить вас всеми подробностями, позвольте мне показать упрощенную версию:

template <typename T>
class sval {

static_assert(std::is_trivially_copy_assignable<T>::value, "");
static_assert(std::is_trivially_destructible<T>::value, "");

static constexpr unsigned align = 64;

unsigned last;
alignas(align) std::atomic<unsigned> serial;

struct alignas(align) {
T value;
} buf[2];

public:

sval(): last(0), serial(last) {}

sval(sval &) = delete;
void operator =(sval &) = delete;

void write(const T &a) {
++last;
buf[last & 1].value = a;
serial.store(last, std::memory_order_release);
}

class reader {

const sval &sv;
unsigned last;

public:

reader(const sval &sv): sv(sv), last(sv.serial.load(std::memory_order_relaxed)) {}

bool read(T &a) {
unsigned serial = sv.serial.load(std::memory_order_acquire);

if (serial == last) {
return false;
}

for (;;) {
a = sv.buf[serial & 1].value;
unsigned check = sv.serial.load(std::memory_order_seq_cst);

if (check == serial) {
last = check;
return true;
}

serial = check;
}
}

};

};

Это общая ценность для одного автора и нескольких читателей. Он содержит два буфера внизу, buf[serial & 1] является «запечатанным», а другой может обновляться. Логика писателя очень проста (и это главная особенность), на которую не влияют присутствие и активность читателей. Но читатель должен сделать больше, чтобы гарантировать согласованность извлеченных данных (и это мой главный вопрос здесь):

  1. читать serial число
  2. читать buf[serial & 1]
  3. читать serial еще раз и повторите попытку, если он был изменен

Так что он может читать данные мусора посередине, но потом проверяет. Возможно ли, что что-то плохое вытечет из read() Внутренности? Если да, то каковы точные причины с аппаратной стороны или где-то еще?

Ниже приведен пример приложения. Я протестировал и это приложение, и мою оригинальную более сложную идею на x86 и ARM. Но ни разнообразие тестов, ни покрытие платформ не дают мне уверенности в своих идеях.

int main() {
const int N = 10000000;
sval<int> sv;
std::thread threads[2];

for (auto &t : threads) {
t = std::thread([&sv] {
sval<int>::reader r(sv);
int n = 0;

for (int i = 0, a; i < N; i = a) {
while (!r.read(a)) {
}

assert(a > i);

++n;
}

std::printf("%d\n", n);
});
}

int dummy[24];

for (int i = 1; i <= N; ++i) {
std::rotate(std::begin(dummy), dummy + 11, std::end(dummy));
sv.write(i);
}

for (auto &t : threads) {
t.join();
}
}

0

Решение

Задача ещё не решена.

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