con ko
con ko

Reputation: 1416

Is thread A guaranteed to finally see a change from thread B in a infinite loop without synchronization?

Supposing I have an unsynchronized atomic flag is_finished. In the following code:

// thread A
while(!is_finished.test(memory_order_relaxed)) {
    // supposing I have some observable side effects here
}
// thread B
is_finished.test_and_set(memory_order_relaxed);

If these two threads run concurrently, will thread A always end?

Upvotes: 0

Views: 131

Answers (2)

Dmytro Ovdiienko
Dmytro Ovdiienko

Reputation: 1156

Because you use memory_order_relaxed mode, you get nothing but the guaranty of the atomic read/write operations themself:

Relaxed operation: there are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed...

Source: https://cppreference.com/

That means you never know when test and test_and_set methods are going to be executed. Compiler and CPU are free to reorder the instructions.

In order to get the desired effect, you should either use memory_order_release/memory_order_acquire semantics on atomics or use std::atomic_thread_fence

Option 1:

// thread A
while(!is_finished.test(memory_order_acquire)) {
    // supposing I have some observable side effects here
}
// thread B
is_finished.test_and_set(memory_order_acq_rel);

Option 2:

// thread 
while(!is_finished.test(memory_order_relaxed)) { 
    std::atomic_thread_fence(memory_order_acquire);
    // supposing I have some observable side effects here
}

// thread B
std::atomic_thread_fence(memory_order_release);
is_finished.test_and_set(memory_order_relaxed);

Please note, you are asking about the single atomic variable. I assume you have more variables that are read/modified based on the value of the atomic variable. You must take care of the ordering all together. Check the Fences are Memory Barriers article for more details.

Upvotes: 1

Brian Bi
Brian Bi

Reputation: 119467

The C++ standard does not guarantee that thread A will see the side effects caused by thread B within any particular amount of time. However, on all real hardware, thread A should see the side effects caused by thread B without undue delay. The standard does say that an implementation "should" ensure that thread A will see the side effect "in a finite period of time", which doesn't actually mean anything, since all time is finite in the real world.

Whether or not you use relaxed memory order makes no difference. The point is that thread A is not going to wake up right away: it will be scheduled whenever the operating system feels like scheduling it. Even if thread A is explicitly waiting on the atomic flag (using the wait member) and thread B notifies all waiters (using the notify member), you still don't know that the operating system is going to wake up thread A right away. A higher-priority task could intervene.

This is why all guarantees provided by the concept of synchronizes with in the standard are conditional: if thread A sees a side effect caused by thread B and the two operations synchronize, then subsequent operations in thread A are guaranteed to also see previous side effects caused by thread B.

Upvotes: 2

Related Questions