Consider the following code snippet taken from Herb Sutter's talk on atomics:
The smart_ptr class contains a pimpl object called control_block_ptr containing the reference count refs.
// Thread A:
// smart_ptr copy ctor
smart_ptr(const smart_ptr& other) {
...
control_block_ptr = other->control_block_ptr;
control_block_ptr->refs.fetch_add(1, memory_order_relaxed);
...
}
// Thread D:
// smart_ptr destructor
~smart_ptr() {
if (control_block_ptr->refs.fetch_sub(1, memory_order_acq_rel) == 1) {
delete control_block_ptr;
}
}
Herb Sutter says the increment of refs in Thread A can use memory_order_relaxed because "nobody does anything based on the action". Now as I understand memory_order_relaxed, if refs equals N at some point and two threads A and B execute the following code:
control_block_ptr->refs.fetch_add(1, memory_order_relaxed);
then it may happen that both threads see the value of refs to be N and both write N+1 back to it. That will clearly not work and memory_order_acq_rel should be used just as with the destructor. Where am I going wrong?
EDIT1: Consider the following code.
atomic_int refs = N; // at time t0.
// [Thread 1]
refs.fetch_add(1, memory_order_relaxed); // at time t1.
// [Thread 2]
n = refs.load(memory_order_relaxed); // starting at time t2 > t1
refs.fetch_add(1, memory_order_relaxed);
n = refs.load(memory_order_relaxed);
What is the value of refs observed by Thread 2 before the call to fetch_add? Could it be either N or N+1? What is the value of refs observed by Thread 2 after the call to fetch_add? Must it be at least N+2?
[Talk URL: C++ & Beyond 2012 - http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-2-of-2 (@ 1:20:00)]
fetch_add
is an atomic operation. Usingmemory_order_relaxed
doesn't change that. – Radiochemicalmemory_order_relaxed
, it would be legal for the compiler to at least start deleting the object before even checking the result of thefetch_sub
as long as it had no effect on the visible behavior of the current thread, which would create a data race. For example, it would be legal to do some of the operations of the destructor for the control block, and then undo them if it found that the ref count wasn't zero. – Radiochemicalfetch_add
as giving you the latest value. Being atomic, thefetch_add
will do read/modify/write in such a way that you can never observe that they are done as three separate operations, however that is independent of when they are done. – Radiochemical