albizzia
albizzia

Reputation: 1

Ordering of non-atomic operations through the use of atomic operations as the basis for the higher-level synchronization facilities

I first cites some description from "C++ concurrency in action" by Anthony Williams :

class spinlock_mutex

{

 std::atomic_flag flag;

public:

 spinlock_mutex():

     flag(ATOMIC_FLAG_INIT)
 {}

 void lock()

 {

     while(flag.test_and_set(std::memory_order_acquire));
 }

 void unlock()

 {

     flag.clear(std::memory_order_release);
 }

};

The lock() operation is a loop on flag.test_and_set() using std::memory_ order_acquire ordering, and the unlock() is a call to flag.clear() with std:: memory_order_release ordering. When the first thread calls lock(), the flag is initially clear, so the first call to test_and_set() will set the flag and return false, indicating that this thread now has the lock, and terminating the loop. The thread is then free to modify any data protected by the mutex. Any other thread that calls lock() at this time will find the flag already set and will be blocked in the test_and_set() loop.

When the thread with the lock has finished modifying the protected data, it calls unlock(), which calls flag.clear() with std::memory_order_release semantics. This then synchronizes-with (see section 5.3.1) a subsequent call to flag.test_and_set() from an invocation of lock() on another thread, because this call has std::memory_order_acquire semantics. Because the modification of the protected data is necessarily sequenced before the unlock() call, this modification happensbefore the unlock() and thus happens-before the subsequent lock() call from the second thread (because of the synchronizes-with relationship between the unlock() and the lock()) and happens-before any accesses to that data from this second thread once it has acquired the lock.

Q: If there are only two threads, and thread A has the object m1 invokes lock() for the first time, and thread B has the object m1 invokes lock() for the first time before m1 invoking unlock() in the thread A, why flag.test_and_set(std::memory_order_acquire) get true rather than false (the initial value) when m1 invokes lock function in the thread B?

I know the release sequence, but constituting a release sequence needs an atomic object invoking an atomic operation with std::memory_order_release and there is no operation invoked with std::memory_order_release.

Upvotes: 0

Views: 219

Answers (3)

ANone
ANone

Reputation: 201

The thread doing things before each other doesn't really make sense other than it has the behaviour you wish to know. The memory_order doesn't come into this. It specifies how regular, non-atomic memory accesses are to be ordered around the atomic operation.

The reasons for having it are that if you do:

lock();
foo();
unlock();

In two threads, foo in one thread cannot read or right before the lock or after the unlock, of the thread in question. This combined with the atomicity of the lock and unlock themselves gives the behaviour we expect. (i.e. no concurrent access from foo).

Upvotes: 1

Caleth
Caleth

Reputation: 63227

There is only one std::atomic_flag. At any one time, it is either set (true) or clear (false).

std::atomic_flag::test_and_set is defined as

Atomically changes the state of a std::atomic_flag to set (true) and returns the value it held before.

When A has called lock, it has changed the flag to set, so the state returned when B tries to lock is set. This is evaluated as the condition of the while, so the loop continues. Thread B will continue "spinning" in this loop until the lock is released

Finally when A calls unlock, the flag is changed to clear. Then B can test again, and the false ends the loop.

Upvotes: 0

MSalters
MSalters

Reputation: 180295

The acquire and release semantics relate to the other (protected) resource, not shown here. In particular, don't move access after the lock or before the unlock. The atomic operations themselves are fully ordered.

Because the operations are fully ordered, your hypothetical order A:lock, B:lock, A:unlock is seen in the same order by both threads. Hence, when thread B calls lock, it sees only the lock from A and not the unlock.

Upvotes: 1

Related Questions