title: SPSC thread safe with fences
body: <p>I just want my code as simple as possible and thread safe.</p>
  
  <p><strong>With C11 atomics</strong>  </p>
  
  <p>Regarding part "7.17.4 Fences" of the ISO/IEC 9899/201X draft</p>
  
  <blockquote>
    <p>X and Y , both operating on some atomic object M, such that A is
    sequenced before X, X modifies M, Y is sequenced before B, and Y reads
    the value written by X or a value written by any side effect in the
    hypothetical release sequence X would head if it were a release
    operation.</p>
  </blockquote>
  
  <p>Is this code thread safe (with "w_i" as "object M") ?<br>
  Are "w_i" and "r_i" need both to be declared as _Atomic ?<br>
  If only w_i is _Atomic, can the main thread keep an old value of r_i in cache and consider the queue as not full (while it's full) and write data ?<br>
  What's going on if I read an atomic without atomic_load ?</p>
  
  <p>I have made some tests but all of my attempts seems to give the right results.
  However, I know that my tests are not really correct regarding multithread : I run my program several times and look at the result.</p>
  
  <p>Even if neither w_i not r_i are declared as _Atomic, my program work, but only fences are not sufficient regarding C11 standard, right ?</p>
  
  <pre><code>typedef int rbuff_data_t;
  
  struct rbuf {
      rbuff_data_t * buf;
      unsigned int bufmask;
  
      _Atomic unsigned int w_i;
      _Atomic unsigned int r_i;
  };
  typedef struct rbuf rbuf_t;
  
  static inline int
  thrd_tryenq(struct rbuf * queue, rbuff_data_t val) {
      size_t next_w_i;
  
      next_w_i = (queue-&gt;w_i + 1) &amp; queue-&gt;bufmask;
  
      /* if ring full */
      if (atomic_load(&amp;queue-&gt;r_i) == next_w_i) {
          return 1;
      }
  
      queue-&gt;buf[queue-&gt;w_i] = val;
      atomic_thread_fence(memory_order_release);
      atomic_store(&amp;queue-&gt;w_i, next_w_i);
  
      return 0;
  }
  
  static inline int
  thrd_trydeq(struct rbuf * queue, rbuff_data_t * val) {
      size_t next_r_i;
  
      /*if ring empty*/
      if (queue-&gt;r_i == atomic_load(&amp;queue-&gt;w_i)) {
          return 1;
      }
      next_r_i = (queue-&gt;r_i + 1) &amp; queue-&gt;bufmask;
      atomic_thread_fence(memory_order_acquire);
      *val = queue-&gt;buf[queue-&gt;r_i];
      atomic_store(&amp;queue-&gt;r_i, next_r_i);
      return 0;
  }
  </code></pre>
  
  <p>I call theses functions as follow :<br>
  Main thread enqueue some data :</p>
  
  <pre><code>while (thrd_tryenq(thrd_get_queue(&amp;tinfo[tnum]), i)) {
      usleep(10);
      continue;
  }
  </code></pre>
  
  <p>Others threads dequeue data :</p>
  
  <pre><code>static void *
  thrd_work(void *arg) {
      struct thrd_info *tinfo = arg;
      int elt;
  
      atomic_init(&amp;tinfo-&gt;alive, true);
  
      /* busy waiting when queue empty */
      while (atomic_load(&amp;tinfo-&gt;alive)) {
          if (thrd_trydeq(&amp;tinfo-&gt;queue, &amp;elt)) {
              sched_yield();
              continue;
          }
          printf("Thread %zu deq %d\n",
                  tinfo-&gt;thrd_num, elt);
      }
  
      pthread_exit(NULL);
  }
  </code></pre>
  
  <p><strong>With asm fences</strong>  </p>
  
  <p>Regarding a specific platform x86 with lfence and sfence,
  If I remove all C11 code and just replace fences by  </p>
  
  <pre><code>asm volatile ("sfence" ::: "memory");
  </code></pre>
  
  <p>and  </p>
  
  <pre><code>asm volatile ("lfence" ::: "memory");
  </code></pre>
  
  <p>(My understanding of these macro is : compiler fence to prevent memory access to be reoganized/optimized + hardware fence)</p>
  
  <p>do my variables need to be declared as volatile for instance ?</p>
  
  <p>I have already seen this ring buffer code above with only these asm fences but with no atomic types and I was really surprised, I want to know if this code was correct.</p>