How to verify a possible execution is OOTA?

2 weeks ago 11
ARTICLE AD BOX

Consider this example:

#include <thread> #include <atomic> int main(){ std::atomic<int> x = 0, y = 0; auto t1 = std::thread([&](){ if(x.load(std::memory_order::relaxed)==42){ // #1 y.store(42,std::memory_order::relaxed); // #2 } }); auto t2 = std::thread([&](){ while(y.load(std::memory_order::relaxed)==0); // #3 x.store(42,std::memory_order::relaxed); // #4 }); t1.join(); t2.join(); }

This is a typical OOTA case that is a variant of the example in [atomics.order] p9. Because the computation of #1 circularly depends on itself.

However, if changing t1 to the following:

auto t1 = std::thread([&](){ if(x.load(std::memory_order::relaxed)==42){ // #1 y.store(42,std::memory_order::relaxed); // #2 }else{ y.store(42,std::memory_order::relaxed); } });

Or, the functionally equivalent form of the above

auto t1 = std::thread([&](){ x.load(std::memory_order::relaxed) // #1 y.store(42,std::memory_order::relaxed); // #2 });

Is the changed example OOTA? Intuitively, I think the subsequent changed examples are not OOTA; however, I cannot explain why. Even though there is no formal definition for what OOTA is, is there an informal way to explain their difference and why the first one is OOTA but the other are not?

I think we cannot distinguish/explain them from the perspective of the control flow. In these three cases, #2 all depends on whether the control flow is returned from #1. In other words, #2 is unreachable if the execution of #1 is not completed(from the perspective of the abstract machine sense).

So, how to explain them?

Read Entire Article