Mahrud
Mahrud

Reputation: 41

C++ memory order for consistently storing in atomic variable from multiple threads

Running the following code hundreds of times, I expected the printed value to be always 3, but it seems to be 3 only about ~75% of the time. This probably means I have a misunderstanding about the purpose of the various memory orders in C++, or the point of atomic operations. My question is: is there a way to guarantee that the output of the following program is predictable?

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

int main () {
  std::atomic<int> cnt{0};
  auto f = [&](int n) {cnt.store(n, std::memory_order_seq_cst);};

  std::vector<std::thread> v;
  for (int n = 1; n < 4; ++n)
    v.emplace_back(f, n);

  for (auto& t : v)
    t.join();

  std::cout << cnt.load() << std::endl;
  return 0;
}

For instance, here's the output statistics after 100 runs:

$ clang++ -std=c++20 -Wall foo.cpp -pthread && for i in {1..100}; do ./a.out; done | sort | uniq -c
      2 1
     21 2
     77 3

Upvotes: 0

Views: 354

Answers (1)

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136515

What you observe is orthogonal to memory orders.

The scheduler cannot guarantee the order of execution of threads with the same priority. Even if CPUs are idle and the threads are assigned to different CPUs, cache misses and lock contention can make threads stall relative to threads on other CPUs. Or, if CPUs are busy running other threads with same or higher priority, then your new threads will have to wait till the running threads exhaust their time slices or block in the kernel, whatever happens earlier is hard for the scheduler to predict. Only if your system has one CPU the new threads will run in expected order relative to each other because they will form one queue on one CPU.

std::memory_order_relaxed is enough here, since you don't require any ordering between the store to cnt and stores/loads to other non-atomic variables. std::atomic is always atomic, std::memory_order specifies whether loads and stores to other non-atomic variables can be reordered relatively to the load or store of an std::atomic variable.

Upvotes: 1

Related Questions