How does the memory_order_relaxed function work to increase the number of atomic link samples in smart pointers?

Consider the following code snippet taken from Herb Sutter's talk about Atomics:

The smart_ptr class contains a pimpl object named control_block_ptr that contains the reference count of 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) == 0) { delete control_block_ptr; } } 

Herb Sutter says that incrementing refs in Thread A can use memory_order_relaxed because "no one does anything based on the action." Now that I understand memory_order_relaxed, if refs is 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 can happen that both threads see the value of refs as N, and both return N + 1. It obviously does not work, and memory_order_acq_rel should be used in the same way as with the destructor. Where am I mistaken?

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 in Thread 2 before calling fetch_add? Could it be N or N + 1? What is the value of refs observed in Thread 2 after calling fetch_add? Should it be at least N + 2?

[URL talk: C ++ and Beyond 2012 - http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-2-of-2 (@ 1:20:00)]

+7
c ++ multithreading c ++ 11 memory-model
source share
3 answers

The Boost.Atomic library, which emulates std::atomic , provides an example and an example of link counting , and this can help you understand.

An increase in the reference counter can always be done using memory_order_relaxed : new links to an object can only be formed from an existing link, and transferring an existing link from one thread to another should already provide any required synchronization.

It is important to provide any possible access to the object in one thread (through an existing link) in order to happen before the object is deleted in another thread. This is achieved through the operation of "release" after deleting the link (any access to the object through this link, obviously, should have happened earlier) and the operation "get" before deleting the object.

It would be possible to use memory_order_acq_rel for the memory_order_acq_rel operation, but this leads to unnecessary β€œget” operations when the reference counter has not yet reached level zero and may impose a performance penalty.

+3
source share

Since this is rather confusing (at least for me), I will partially address one question:

(...), then it can happen that both threads see that the value of refs is N, and both return N + 1 (...)

According to @AnthonyWilliams in this answer , the above sentence seems wrong like:

The only way to ensure that you have a "last" value is to use a read-modify-write operation such as exchange (), compare_exchange_strong (), or fetch_add (). Read-modify-write operations have the additional restriction that they always work with the "last" value, therefore the sequence of operations ai.fetch_add (1) with a series of streams returns a sequence of values ​​without duplicates or spaces. In the absence of additional restrictions, there is still no guarantee which streams will see which values.

So, given the authority argument, I would say that it is impossible for both threads to see a value going from N to N + 1.

+2
source share

From C ++ help on std::memory_order :

memory_order_relaxed: Relaxed operation: no synchronization or order restrictions imposed on other reads or writes, only this operational atomicity is guaranteed

Below is an example below on this page .

Basically, std::atomic::fetch_add() is still atomic, even if with std::memory_order_relaxed , so parallel refs.fetch_add(1, std::memory_order_relaxed) from two different threads will always increase refs by 2. A memory order point is how other non-atomic or std::memory_order_relaxed atomic operations can be reordered around the current atomic operation with the specified memory order.

+2
source share

All Articles