![](/img/trans.png)
[英]Lock free single producer/single consumer circular buffer - Can CPU speculation break the memory barrier logic?
[英]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.