Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading pointers from another thread in C++

In the code below, the value of x in thread 2 will always be 10, because of the atomic thread fences.

int x;
atomic<bool> b(false);

// thread 1:
x = 10;
atomic_thread_fence(memory_order_release);
b = true;

// thread 2:
while(!b){}
atomic_thread_fence(memory_order_acquire);
assert(x == 10); // x will always be 10

But in the following code, will *x always be 10 in thread 2?

int* x = new int;
atomic<bool> b(false);

// thread 1:
*x = 10;
atomic_thread_fence(memory_order_release);
b = true;

// thread 2:
while(!b){}
atomic_thread_fence(memory_order_acquire);
assert(*x == 10); // will *x always be 10?
like image 274
Bernard Avatar asked Feb 06 '23 01:02

Bernard


1 Answers

In both cases you get 10, there is no difference here whether the store is done directly or through a pointer.

You do not need the memory fence here because b = true is essentially b.store(true, std::memory_order_seq_cst) - an acquire-release with a fence.

Such a memory order prevents the compiler from reordering stores and loads around the operation and makes the preceding stores visible to other threads when this store becomes visible.

If you compare the generated code of these two functions:

#include <atomic>

int x;
std::atomic<bool> b(false);

void f() {
    x = 10;
    std::atomic_thread_fence(std::memory_order_release);
    b = true;
}

void g() {
    x = 10;
    b = true;
}

it is identical:

f():
        movl    $10, x(%rip)
        movb    $1, b(%rip)
        mfence
        ret
g():
        movl    $10, x(%rip)
        movb    $1, b(%rip)
        mfence
        ret

In your particular case though, it seems to me that you need no more than std::memory_order_release store to b to make the store to x also visible to other threads, the fence is unnecessary. I.e. b.store(true, std::memory_order_release) is enough here. The consumer code needs to do b.load(std::memory_order_acquire).

Standard mutexes do acquire memory order on lock and release memory order on unlock (this is where terms acquire/release come from), there are no fences involved.

It is very rare that an explicit fence is required, mostly in hardware drivers. In user-space mode code fences are often put because of misunderstanding of C++11 memory model. Fences are the most expensive atomic synchronization mechanism, this is the main reason they are avoided.

like image 124
Maxim Egorushkin Avatar answered Feb 07 '23 18:02

Maxim Egorushkin