Can you split an atomic load-store with stricter memory ordering into separate relaxed load-stores along with memory barrier instructions?

Viewed 110

Here is a simple example of acquire-release semantics used for data synchronization across threads.

// thread 1                                     // thread 2
data = 100;
flag.store(true, std::memory_order_release);
                                                while(!flag.load(std::memory_order_acquire));
                                                assert(data == 100);

As I understand, this is accurately showing the use of acquire-release memory ordering and program will work as intended.

But what is the case if I were to use standalone barriers?

// thread 1                                     // thread 2
data = 100;
std::atomic_thread_fence(std::memory_order_release);
flag.store(true, std::memory_order_relaxed);
                                                while(!flag.load(std::memory_order_relaxed))
                                                    std::atomic_thread_fence(std::memory_order_acquire);
                                                assert(data == 100);

I always thought that this is exactly equivalent to the first example.

But today I watched a talk at CppCon by Herb Sutter (C++ and Beyond 2012: Herb Sutter - atomic Weapons). At 1:07:10 in the video, he gives an example to show that standalone fences are suboptimal. After seeing that I am confused.

The example is this:

   // thread 1                                     // thread 2
   widget *temp = new widget();
XX mb();  XXXXXXXXXXXXXXXXXXXXX  // a
   global = temp;
                                                   temp2 = global;
                                                XX mb();  XXXXXXXXXXXXXXXXXXXXX  // b
                                                   temp2->do_something();
                                                   temp2 = global;
                                                XX mb();  XXXXXXXXXXXXXXXXXXXXX
                                                   temp2->do_something_else();

He says that, at a and b, you need a full barrier, not just release and acquire, since those are not associated with any particular stores or loads. Furthermore, he says that, standalone acquire and release barriers doesn't make any sense. Is this correct? (For simplicity, it is assumed that, reads and writes to global are indivisible, i.e. global never contains a torn value).

Why this does not work?

   // thread 1                                     // thread 2
   widget *temp = new widget();
XX release();  XXXXXXXXXXXXXXXX  // a
   global = temp;
                                                   temp2 = global;
                                                XX acquire();  XXXXXXXXXXXXXXXX  // b
                                                   temp2->do_something();
                                                   temp2 = global;
                                                XX acquire();  XXXXXXXXXXXXXXXX
                                                   temp2->do_something_else();
0 Answers
Related