Reputation: 1
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
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
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
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