visitor
visitor

Reputation: 111

Why does this acquire and release memory fence not give a consistent value?

im just exploring the use of acquire and release memory fences and dont understand why i get the value output to zero sometimes and not the value of 2 all the time

I ran the program a number of times , and assumed the atomic store before the release barrier and the atomic load after the acquire barrier would ensure the values always would synchronise

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int>x;



void write()
{


    x.store(2,std::memory_order_relaxed);

    std::atomic_thread_fence(std::memory_order_release);



}

void read()
{

    std::atomic_thread_fence(std::memory_order_acquire);

    // THIS DOES NOT GIVE THE EXPECTED VALUE OF 2 SOMETIMES
    std::cout<<x.load(std::memory_order_relaxed)<<std::endl; 

}

int main()
{

std::thread t1(write);
std::thread t2(read);
t1.join();
t2.join();
return 0;
}

the atomic varible x gives a value of 0 sometimes

Upvotes: 1

Views: 899

Answers (4)

perencia
perencia

Reputation: 1542

0 and 2 are both valid outcomes. The purpose of memory ordering is to ensure that all observers view memory operations in the same order, not that the order is always the same.

Upvotes: 0

curiousguy
curiousguy

Reputation: 8270

The reasons is simple: your fences accomplish exactly nothing and cannot have any use here anyway because there is no write that the fence would make visible (on the release side) to the acquiring side.

The simple answer is that the reading thread can run first and obviously will not see any write if it does.

The longer answer is that when your code has a race, as any code that uses either mutexes or atomics in a non trivial way, it must be prepared for any race outcome! So you has to make sure that not reading the value written by a write will not break your code.

ADDITIONAL EXPLANATION

A way to explain rel/ack semantics is:

  • release means "I have accomplished something" and I set that atomic object to some value to publish that claim;
  • acquire means "have you accomplished something?" and I read that atomic object to see if it contains the claim.

So release before you have accomplished anything is meaningless and an acquire that throws away the information containing the claim, as in (void)x.load(memory_order_acquire) is generally meaningless as there is no knowledge (in general) of what was acquired, which is to say of what was accomplished. (The exception to that rule is when a thread had relaxed loads or RMW operations.)

Upvotes: 0

Eric
Eric

Reputation: 936

I think you are misunderstanding the purpose of fences. Fences only enforce a certain ordering of memory operations for the compiler and processor in a single thread of execution. Your acquire fence will not magically make the thread wait until the óther thread performs the release.

Some literature will describe that a release operation in one thread "synchronizes with" a subsequent acquire operation in another thread. The key to this is that the acquire action is a subsequent action (i.e. the acquire is ordered "after" the release). If the release action is ordered after your acquire action, then there is no synchronizes-with relation between the write and read operations.

The reason why your code doesn't consistently return what you're expecting is because the thread interleavings sometimes order the write before the read, sometimes the read before the write.

If you want to guarantee that thread t2 reads the value 2 that thread t1 publishes, you're going to have to force t2 to wait for the publish to happen. The textbook example almost invariably uses a guard variable that notifies t2 that the data is ready to be consumed.

I recommend you read a very well-written blog post about release and acquire semantics and the synchronizes-with relation at Preshing on Programming's The Synchronizes-With Relation.

Upvotes: 3

Dmitry Kuzminov
Dmitry Kuzminov

Reputation: 6584

Looks like you misuse the fence. You are trying to use it as a mutex, right? If you expect the code to output 2 always, you just think that the load operation would never be executed before the save one. But that is not what memory fence does, that is what the synchronization primitives do.

The fences are much trickier and they just don't allow compiler/processor to reorder certain types of commands within one thread. At the end of the day the order of execution of two separate threads is undefined.

Upvotes: 1

Related Questions