I'm playing with lock-free algorithms in C and C++ and recently stumbled upon a behavior I don't quite understand. If you have the following code, running it will give you something like
reader started writer started iters=79895047, less=401131, eq=48996928, more=30496988
Aren't std::atomics
are expected to be sequentially-consistent? If so, why does the reader sometimes see b
being updated before a
? I also tried to do various tricks involving memory fences with no success. The full compilable code can be seen at https://github.com/akamaus/fence_test
What's wrong with the example?
std::atomic<uint> a(0);
std::atomic<uint> b(0);
volatile bool stop = false;
void *reader(void *p) {
uint64_t iter_counter = 0;
uint cnt_less = 0,
cnt_eq = 0,
cnt_more = 0;
uint aa, bb;
printf("reader started\n");
while(!stop) {
iter_counter++;
aa = a.load(std::memory_order_seq_cst);
bb = b.load(std::memory_order_seq_cst);
if (aa < bb) {
cnt_less++;
} else if (aa > bb) {
cnt_more++;
} else {
cnt_eq++;
}
}
printf("iters=%lu, less=%u, eq=%u, more=%u\n", iter_counter, cnt_less, cnt_eq, cnt_more);
return NULL;
}
void *writer(void *p) {
printf("writer started\n");
uint counter = 0;
while(!stop) {
a.store(counter, std::memory_order_seq_cst);
b.store(counter, std::memory_order_seq_cst);
counter++;
}
}
Sequentially consistent memory ordering implies that the modification order (of the atomic objects manipulated with seq cst) observed by all threads is consistent. The program behaves as if all those operations happen interleaved in a single total order. Consider the following cases:
Writer Reader a == 0 a = 1 b = 1 b == 1
Result: aa < bb
.
Writer Reader a = 1 a == 1 b == 0 b = 1
Result: aa > bb
With a lock, e.g. a mutex
, you can make sure that the operations don't interleave.