无锁单生产者/单消费者循环缓冲区

Qza*_*aac 6 c++ multithreading atomic lock-free c++11

当我无法弄清楚为什么需要特定的内存屏障时,我一直在本网站上查看无锁的单生产者/单消费者循环缓冲区。我已经仔细阅读了数百次关于内存顺序的标准规则,但我不明白我错过了什么。

通过这种实现,只有一个可以调用该push()函数的唯一线程和另一个可以调用该函数的唯一线程pop()

这是Producer代码:

bool push(const Element& item)
{       
  const auto current_tail = _tail.load(std::memory_order_relaxed);  //(1)
  const auto next_tail = increment(current_tail);

  if(next_tail != _head.load(std::memory_order_acquire))            //(2)               
  {     
    _array[current_tail] = item;                                    //(3)
    _tail.store(next_tail, std::memory_order_release);              //(4)
    return true;
  }
  return false; // full queue
}
Run Code Online (Sandbox Code Playgroud)

这是Consumer代码:

bool pop(Element& item)
{
  const auto current_head = _head.load(std::memory_order_relaxed);    //(1)
  if(current_head == _tail.load(std::memory_order_acquire))           //(2)
    return false; // empty queue

  item = _array[current_head];                                       //(3)
  _head.store(increment(current_head), std::memory_order_release);   //(4)
  return true;
}
Run Code Online (Sandbox Code Playgroud)

我理解为什么绝对需要theProducer (4)和 theConsumer (2)语句,这是因为我们必须确保一旦将看到存储的值,所有在by之前发生的写入都将是可见的副作用。(4) released storeProducerconsumer

我也明白为什么Consumer (4)需要这个语句,这是为了确保Consumer (3)在执行Consumer (4)存储之前执行加载。

问题

  • 为什么Producer (2)需要使用获取语​​义(而不是轻松)执行加载?是否要防止Producer (3) or (4)在条件之前(在编译时或运行时)被重新编码?

RbM*_*bMm 5

我们需要证明

_array[current_tail] = item; // push(3)
Run Code Online (Sandbox Code Playgroud)

符合执行( current_head == current_tail)

item = _array[current_head]; // pop(3)
Run Code Online (Sandbox Code Playgroud)

完成了。仅当单元格中的数据已复制到项目后,我们才能覆盖单元格

_head.load(std::memory_order_acquire) // push(2)
Run Code Online (Sandbox Code Playgroud)

同步于

_head.store(increment(current_head), std::memory_order_release);   //pop(4)
Run Code Online (Sandbox Code Playgroud)

通过释放-获取排序:

一旦原子加载获取 ( push (2) ) 完成,在原子存储释放 ( pop(4) )之前发生的所有内存写入 ( pop(3) _head) 都会产生可见的副作用。_head

因此, push(2)完成后的生产者代码,保证可以看到pop(3)的结果。这意味着数据被复制到 item 并且此操作的结果对于Push(2)_array[current_head]之后的生产者代码可见,因此已经空闲。_array[current_head]

从加载描述的另一面来看-当前线程中的memory_order_acquire读取或写入 ( push(3) ) 在此加载之前不能重新排序。所以push(3)将在push(2)加载完成后执行,但此时pop(3)已经完成

item = _array[current_head];                                        //pop(3)
_head.store(increment(current_head), std::memory_order_release);    //pop(4)
-----
    _head.load(std::memory_order_acquire);                          //push(2)
    _array[current_tail] = item;                                    //push(3)         
Run Code Online (Sandbox Code Playgroud)