这个问题的灵感来自 Lock-free Progress Guarantees 。显示的代码并不是严格无锁的。每当写入线程在队列不为空或未满时挂起时,读取线程都会返回 false,从而阻止整个数据结构取得进展。
真正无锁环形缓冲区的正确行为应该是什么?
Generally, truly lock-free algorithms involve a phase where a pre-empted thread actually tries to ASSIST the other thread in completing an operation.
有关于此技术的引用吗?如何针对基于数组的 MPMC 队列实现它?
我查看了一些代码,它们也有类似的问题。
- craflin's version使用两个内部原子来记录读取和写入。
- https://codereview.stackexchange.com/questions/263157/c-lock-free-mpmc-ring-buffer-in-c20使用忙等待,而不是无锁。
最佳答案
作为跨线程辅助如何在现实生活中最终发挥作用的一个很好的例子,考虑可以通过更改 liblfds
来获得无锁 MPMC 队列。算法大致如下:
使用 3 个计数器:
-
alloc_pos
:已启动的推送操作总数。当推送开始时,该值会自动递增。 -
write_pos
:已知较低位置的所有写入操作均已完成。 -
read_pos
:所有写在较低位置的元素都已被消耗。
在此方案中,推送或弹出操作由受影响插槽中的 CAS 完成。 write_pos
和read_pos
变量是多余的。
因此,要推送,线程首先递增 alloc_pos
,然后递增 write_pos
过去它前面所有能看到的槽位都已完成。这是一个辅助——它正在完成之前在其他线程中启动的写入。然后线程必须扫描 write_pos
之间的槽。和alloc_pos
直到它找到一个空闲的并设法通过 CAS 保留它。
要弹出,读取器首先递增 read_pos
过去它可以看到的所有写在较低位置的项目都被消耗掉了。这又是一个帮助——完成之前的阅读。然后它从read_pos
开始扫描至alloc_pos
查看是否可以找到已完成写入的项目。
正如评论中提到的,真正做到这一点会很烦人,因为实现决策会权衡性能与您需要的排序和可用性保证,以及跳过障碍以防止 ABA 问题。
关于c++ - 真正无锁 MPMC 环形缓冲区?线程能够互相协助以避免阻塞吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71725753/