Reputation: 41
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
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