繁体   English   中英

锁定单个生成器/单个使用者循环缓冲区

[英]Lock free single producer/single consumer circular buffer

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

通过这种实现,只有一个可以调用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
}

这是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;
}

我理解为什么绝对需要Producer (4)Consumer (2)语句,这是因为我们必须确保Producer(4) released store 之前发生的所有写入都是consumer可见的副作用会看到储值。

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

这个问题

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

我们需要证明这一点

_array[current_tail] = item; // push(3)

符合current_head == current_tail

item = _array[current_head]; // pop(3)

完成了。 只有在数据已经复制到项目之后,我们才能覆盖单元格

_head.load(std::memory_order_acquire) // push(2)

同步

_head.store(increment(current_head), std::memory_order_release);   //pop(4)

通过发布 - 获取订购:

_head_head原子加载 - 获取( push(2) )后,在_head上的原子存储释放( pop(4) )之前发生的所有内存写入( pop(3)_head变为可见的副作用。

所以push(2)完成后的生产者代码,保证看到pop(3)的结果 这意味着来自_array[current_head]数据被复制到item,并且此操作的结果在push(2)之后对Producer代码可见,因此_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)         

内存障碍阻止CPU重新排序对Element对象的访问,这些访问不使用互锁,跨越对队列结构的访问(这里使用索引实现,但指针同样可行)。

使用编号,(3)在(2)和(4)之间执行是必要的,并且存储屏障提供了这一点。

您询问生产者中(2)-vs-(3)的确切情况可防止在队列满(推荐的站点与有效数据重叠)时推测性地覆盖有效数据。 如果没有屏障,即使条件失败,原始数据也会从Producer线程的角度恢复,消费者可能会短暂地看到中间值。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM