title: atomic_thread_fence requirement in a sequence lock
body: <p>I'm writing a simple <a href="https://en.wikipedia.org/wiki/Seqlock" rel="nofollow noreferrer">sequence lock </a> and after reading through the c++ docs on atomics and atomic_thread_fence, it feels like I should need a fence.  However, Ive written it without a fence and I have a fairly rigorous test that seems to pass without it.</p>
  
  <p>The basic sequence lock concept goes like this.  You have a single writer that constantly updates some piece of shared memory in place.  It associates an incrementing sequence number with the data.  Before it starts writing it increments once, when its done writing it increments again.</p>
  
  <p>On the reader end, before it starts looking at the data it waits for the sequence number to turn even ( if its odd that means we are mid-write ).  It then keeps track of the sequence number it sees at the start, copies out the data locally, then checks the sequence number again when its done.  If they match, then the data is clean and usable.  If they dont match, then we are susceptible to a partial-read and we try again.</p>
  
  <p>The sequencer number is stored in an std::atomic.  The writer updates it with a store( std::memory_order_release ) at the beginning and end of the block.
  The reader reads the sequence number with a load( std::memory_order_acquire ) at the beginning and end of the read.</p>
  
  <p>The data in question to be copied form writer -> reader is completely independent of the atomic seqno, which is why I was under the impression I may need a fence here to synchronize the dependency-less data... but it seems to work no matter what I throw at it.
  I am running on a multi-socket / multi-core Intel Xeon machine, maybe it works specifically on this CPU?</p>
  
  <p>Code snippets:</p>
  
  <pre><code>void SequenceLock::writeLock()
  {
      m_seqno.store( m_seqno.load( std::memory_order_relaxed ) + 1, std::memory_order_release );
  }
  
  void SequenceLock::writeUnlock()
  {
      m_seqno.store( m_seqno.load( std::memory_order_relaxed ) + 1, std::memory_order_release );
  }
  
  uint64_t SequenceLock::enterRead() const
  {
      uint64_t seqno;
      while( ( seqno = m_seqno.load( std::memory_order_acquire ) ) &amp; 1 ) {} //no yield, expect readers to be pinned
      return seqno;
  }
  
  bool SequenceLock::validateRead( uint64_t prevSeqno ) const
  {
      return m_seqno.load( std::memory_order_acquire ) == prevSeqno;
  }
  
  
  //sample writer
  lock.writeLock()
  //update some shared data
  lock.writeUnlock()
  
  //sample reader
  uint64_t seqno;
  do {
      seqno = lock.enterRead()
      //copy shared data locally
  } while( !lock.validateRead( seqno ) )
  </code></pre>
  
  <p>EDIT: Stranger still, I changed all the memory accessed above to memory_order_relaxed and it <em>still</em> seemed to work... odd</p>