С++ атомарное чтение/запись недоразумения

Почему программа с этим кодом иногда печатает "2"?

int main() {
    std::atomic<int> a;
    a = 0;

    std::thread t1([&]{++a;});
    std::thread t2([&]{a++;});
    std::thread t3([&]{
        a = a.load() + 1;
    });

    t1.join();
    t2.join();
    t3.join();

    if (a != 3) {
        std::cout << "a: " << a << std::endl;
    }
}

Я думал, что std::atomic гарантирует, что все операции будут выполняться атомарно, поэтому запись здесь (приращение) будет использовать барьер памяти, и мы будем всегда 3 в конце работы потоков. Я изучил этот код и выяснил, что поток проблем t3, но я не могу понять, почему это неправильно.

Ответы

Ответ 1

t3, в отличие от двух других потоков, не выполняет атомную добавку. Вместо этого он атомарно загружает a, выполняет арифметику (добавляет 1) на временную и атомарно сохраняет это новое значение обратно в a. Это перезаписывает a независимо от атомных операций, которые могли произойти между ними.

Таким образом, вы можете иметь следующий сценарий:

  • t1 или t2 атомное приращение a, которое теперь равно 1.
  • t3 атомарно загружается 1.
  • t1 или t2 атомное приращение a, которое теперь равно 2.
  • t3 выполняет неатомную добавку на ранее загруженном значении и атомарно сохраняет назад 2.