<p>Yes, the second version with acquire/release order for the operations on <code>m_instance</code> is correct and equivalent to the first version. In fact, it is even preferable to the first version, because fences affect all preceding (acquire)/succeeding (release) atomic operations, but you only need synchronization of operations on <code>m_instance</code>. That's why on some architectures explicit fences are slower.</p>
<hr />
<p>Why do you need acquire/release in the first place? Because you need a happens-before relation between creation of the singleton and usage of the singleton to avoid data races. Suppose the following:</p>
<ul>
<li>Thread 1 calls <code>Singleton::getInstance()</code> and initializes it; this involves creating a new object and storing the pointer in <code>m_instance</code></li>
<li>Thread 2 calls <code>Singleton::getInstance()</code> and observes the pointer written by Thread 1</li>
</ul>
<p>Thread 2 will most like <em>dereference</em> the pointer to access the object it points to, and this access is most likely non-atomic. So you have two non-atomic accesses to the object - one during creation and one when using the object. If these are not ordered by a happens-before relation, then this is a data race.</p>
<p>So how do we establish a happens-before relation? By storing the pointer with <code>memory_order_release</code>, and reading it with <code>memory_order_acquire</code>. When an acquire-load operation observes the value written by a release-store, the load <em>synchronizes-with</em> the store, thereby establishing a happens-before relation. Further, construction of the object is <em>sequenced-before</em> the store, and the load is sequenced-before dereferenciation (sequenced-before also implies happens-before), and since happens-before is <em>transitive</em>, it follows that construction happens-before dereferenciation.</p>
<p>For more details on the C++ memory model I recommend this paper which I have co-authored: <a href="https://arxiv.org/abs/1803.04432" rel="nofollow noreferrer">Memory Models for C/C++ Programmers</a></p>
why c++ singleton need memory_order_acquire
<pre><code>Singleton* Singleton::getInstance() {
     Singleton* tmp = m_instance.load(std::memory_order_relaxed);
     std::atomic_thread_fence(std::memory_order_acquire);        //&lt;--1
     if (tmp == nullptr) {
         std::lock_guard&lt;std::mutex&gt; lock(m_mutex);
         tmp = m_instance.load(std::memory_order_relaxed);
         if (tmp == nullptr) {
             tmp = new Singleton;
             assert(tmp != nullptr);    
             std::atomic_thread_fence(std::memory_order_release); //&lt;--2
             m_instance.store(tmp, std::memory_order_relaxed);
         }
     }
     return tmp;
 }
</code></pre>
<p>here is a common c++ singleton implementation, there are a <code>release fence</code> in <code>2</code>(marked as above), it is easy to understand, it prevents from reordering <code>new Singleton</code>, without this fence, another thread might get an instance without executing construction yet;</p>
<p>what confuses me is that the <code>acquire fence</code> in <code>1</code>,  <code>release fence</code> promises that Singleton construction has been executed then store to m_instance, here, when we fetch instance, we won't get an instance without executing construction, why do we still need a <code>acquire fence</code> in <code>1</code>?</p>
<p>And, can we replace <code>atomic_thread_fence</code> with m_instance operation memroy order, are they same? (show as below)</p>
<pre><code>Singleton* Singleton::getInstance() {
     Singleton* tmp = m_instance.load(std::memory_order_acquire);
     if (tmp == nullptr) {
         std::lock_guard&lt;std::mutex&gt; lock(m_mutex);
         tmp = m_instance.load(std::memory_order_relaxed);
         if (tmp == nullptr) {
             tmp = new Singleton;
             assert(tmp != nullptr);    
             m_instance.store(tmp, std::memory_order_release);
         }
     }
     return tmp;
 }

</code></pre>