首页 > 解决方案 > 无锁、无等待、单生产者单消费者环形缓冲区是否可能在溢出时覆盖最近的数据?

问题描述

一段时间以来,我一直在研究无锁、无等待、C++ SPSC 环形缓冲区实现。对于缓冲区已满时的特别排队操作,我遇到了几种不同的方法来处理这种情况。比如这家伙对push和pop操作的实现如下:

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
}

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

他的方法只是在缓冲区已满时拒绝将新元素排队,这很好。之后,我一直在寻找一种方法来将此实现转换为在缓冲区耗尽时覆盖最近的数据。要做到这一点,根据这个家伙,当队列已满时,_head 索引也必须通过 push 操作推进。但是现在,两个线程能够写入必须同步的同一个变量。由于我不是 C++ 专家,我不能简单地弄清楚这是否可以通过添加一些原子加载或存储操作来简单地完成,但我的直觉告诉我这样的事情是不可能的,因为即使 _head 和 _tail 同步是调整得很好,那么 pop() 可能会得到一个被推送部分设置(或填充)的元素,即。两个函数中标记为“3”的行将不是原子的,并且可能出现肮脏的竞争条件。此外,我在 GitHub 和 Google 搜索中找不到这样的实现。

那么,这样的 SPSC 环形缓冲区是否可以实现保留无锁和无等待的特性呢?如果可能的话,我恳请您提供简明的说明或代码。另外,您能否就我在推送和弹出功能上进行转换的方法发表您的意见?

标签: c++multithreadingc++11memory-modelcircular-buffer

解决方案


推荐阅读