简体   繁体   中英

C++11 Atomic memory order with non-atomic variables

I am unsure about how the memory ordering guarantees of atomic variables in c++11 affect operations to other memory.

Let's say I have one thread which periodically calls the write function to update a value, and another thread which calls read to get the current value. Is it guaranteed that the effects of d = value; will not be seen before effects of a = version; , and will be seen before the effects of b = version; ?

atomic<int> a {0};
atomic<int> b {0};
double d;

void write(int version, double value) {
    a = version;
    d = value;
    b = version;
}

double read() {
    int x,y;
    double ret;
    do {
        x = b;
        ret = d;
        y = a;
    } while (x != y);
    return ret;
}

Your object d is written and read by two threads and it's not atomic. This is unsafe, as suggested in the C++ standard on multithreading:

1.10/4 Two expression evaluations conflict if one of them modifies a memory location and the other one accesses or modifies the same memory location.

1.10/21 The execution of a program contains a data race if it contains two conflicting actions in different threads,at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.

Important edit:

In your non-atomic case, you have no guarantees about the ordering between the reading and the writing. You don't even have guarantee that the reader will read a value that was written by the writer (this short article explains the risk for non-atomic variables).

Nevertheless , your reader's loop finishes based on a test of the surrounding atomic variables, for which there are strong guarantees. Assuming that version never repeats between writer different calls, and given the reverse order in which you aquire their value:

  • the order of the d read compared to the d write can't be unfortunate if the two atomics are equal.
  • similarly, the read value can't be inconsistent if the two atomics are equal.

This means that in case of an adverse race condition on your non-atomic, thanks to the loop, you'll end-up reading the last value .

The rule is that, given a write thread that executes once, and nothing else that modifies a , b or d ,

  • You can read a and b from a different thread at any time, and
  • if you read b and see version stored in it, then
    • You can read d ; and
    • What you read will be value .

Note that whether the second part is true depends on the memory ordering; it is true with the default one ( memory_order_seq_cst ).

Is it guaranteed that the effects of d = value; will not be seen before effects of a = version; , and will be seen before the effects of b = version; ?

Yes, it is. This is because sequensial consistency barrier is implied when read or write atomic<> variable.

Instead of storing version tag into two atomic variables before value's modification and after it, you can increment single atomic variable before and after modification:

atomic<int> a = {0};
double d;

void write(double value)
{
     a = a + 1; // 'a' become odd
     d = value; //or other modification of protected value(s)
     a = a + 1; // 'a' become even, but not equal to the one before modification
}

double read(void)
{
     int x;
     double ret;
     do
     {
         x = a;
         ret = value; // or other action with protected value(s)
     } while((x & 2) || (x != a));
     return ret;
}

This is known as seqlock in the Linux kernel: http://en.wikipedia.org/wiki/Seqlock

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM