Home > Net >  C atomics reading stale value
C atomics reading stale value

Time:07-20

I'm reading the C Concurrency in Action book and I'm having trouble understanding the visibility of writes to atomic variables.

Lets say we have a

std::atomic<int> x = 0;

and we read/write with sequential consistent ordering

1.   x;        
   // <-- thread 2
2. if (x == 1) {
   // <-- thread 1
   }

If we have 2 threads that execute the code above. Is it possible that thread 1 arrives at line 2. and reads x == 1, after thread 2 already executed line 1.? So does the sequential consistent x of thread 2 instantly gets propagated to thread t1 or is it possible that thread 1 reads a stale value x == 1?

I think if we use relaxed_ordering or acq/rel the above situation is possible, but how about the sequential consistent ordering?

CodePudding user response:

If you're thinking that multiple atomic operations are somehow safely grouped, you're wrong. They'll always occur in order within that thread, and they'll be visible in that order, but there is no guarantee that two separate operations will occur in one thread before either occurs in the other.

So for your specific question "Is it possible that thread 1 arrives at line 2. and reads x == 1, after thread 2 already executed line 1.?", the answer is yes, thread 1 could reach the x == 1 test after thread 2 has incremented x as well, so x would already be 2 and neither thread would see x == 1 as true.

The simplest way to think about this is to imagine a single processor system, and consider what happens if the running thread is switched out at any time aside from the middle of a single atomic operation.

So in this case, the operations (inc1 and test1 for thread 1 and inc2 and test2 for thread 2) could occur in any of the following orders:

  • inc1 test1 inc2 test2
  • inc1 inc2 test1 test2
  • inc1 inc2 test2 test1
  • inc2 inc1 test1 test2
  • inc2 inc1 test2 test1
  • inc2 test2 inc1 test1

As you see, there is no possibility of either test occurring before either increment, nor can both tests pass (because the only way a test passes is if the increment associated with it on that thread has occurred but not the increment on the other thread), but there's no guarantee any test passes (both increments could precede both tests, causing both tests to test against the value 2 and neither test to pass). The race window is narrow, so most of the time you'd probably see exactly one test pass, but it's wholly possible to get unlucky and have neither pass.

If you want to make this work reliably, you need to make sure you both modify and test in a single operation, so exactly one thread will see the value as being 1:

if (  x == 1) {  // The first thread to get here will do the stuff
   // Do stuff
}

In this case, the increment and read are a single atomic operation, so the first thread to get to that line (which might be thread 1 or thread 2, no guarantees) will perform the first increment with x atomically returning the new value which is tested. Two threads can't both see x become 1, because we kept both increment and test as one operation.

That said, if you're relying on the content of that if being completed before any thread executes code after the if, that won't work; the first thread could enter the if, while the second thread arrives nanoseconds later and skips it, realizing it wasn't the first to get there, and it would immediately begin executing the code after the if even if the first thread hasn't finished. Simple use of atomics like this is not suited for a "run only once" scenario that people often write this code for when the "run only once" code must be run exactly once before dependent code is executed.

CodePudding user response:

Let's simplify your question.

When 2 threads execute func()

std::atomic <int> x=0;
void func()
{
   x;
 std::cout << x;
}

Following result is possible?

11

And the answer is NO! Only "12" or "21" is possible.

The sequential consistency on a atomic variable works as you want on this simple case.

  • Related