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)]
Boost.Atomic library that emulates
std::atomic
provides similar reference counting example and explanation, and it may help your understanding.