简体   繁体   English

真正的无锁 MPMC 环形缓冲区? 线程可以互相协助避免阻塞吗?

[英]Truly Lock-free MPMC Ring Buffer? Threads able to assist each other to avoid blocking?

This question is inspired by Lock-free Progress Guarantees .这个问题的灵感来自Lock-free Progress Guarantees The code shown is not strictly lock-free.所示代码并非严格无锁。 Whenever a writer thread is suspended when the queue is not empty or not full, the reader threads returned false, preventing the whole data structure from making progress.每当写入线程在队列不为空或未满时挂起,读取线程就会返回 false,从而阻止整个数据结构取得进展。
What should be the correct behavior for a truly lock-free ring buffer?真正无锁环形缓冲区的正确行为应该是什么?

Generally, truly lock-free algorithms involve a phase where a pre-empted thread actually tries to ASSIST the other thread in completing an operation.通常,真正的无锁算法涉及一个阶段,在该阶段中,被抢占的线程实际上会尝试协助其他线程完成操作。

Any reference to this technique?对这种技术有任何参考吗? How could it be implemented for an array-based MPMC queue?如何为基于数组的 MPMC 队列实现它?

I looked at some codes, and they have similar problems.我看了一些代码,他们有类似的问题。

As a good example of how cross-thread assist often ends up working in real life, consider that a lock-free MPMC queue can be obtained by changing the liblfds algorithm along these lines:作为跨线程辅助如何在现实生活中最终起作用的一个很好的例子,考虑一个无锁的 MPMC 队列可以通过改变liblfds算法来获得:

Use 3 counters:使用 3 个计数器:

  • alloc_pos : the total number of push operations that have been started. alloc_pos :已启动的推送操作总数。 This is incremented atomically when a push starts.当推送开始时,这会自动增加。
  • write_pos : all write operations at lower positions are known to be complete. write_pos :已知较低位置的所有写操作已完成。
  • read_pos : all items written at lower positions are known to have been consumed. read_pos :已知在较低位置写入的所有项目已被消耗。

In this scheme, a push or pop operation is completed by a CAS in the affected slot.在此方案中,推送或弹出操作由受影响槽中的 CAS 完成。 The write_pos and read_pos variables are redundant . write_posread_pos变量是多余的。

So to push, a thread first increments alloc_pos , and then increments write_pos past all the slots in front of it that it can see are complete.因此,要推送,线程首先递增alloc_pos ,然后递增write_pos超过它前面的所有槽,它可以看到它是完整的。 This is an assist -- it is completing previous writes that were started in other threads.这是一个辅助——它正在完成之前在其他线程中启动的写入。 Then the thread has to scan the slots between write_pos and alloc_pos until it finds a free one and manages to reserve it with a CAS.然后线程必须扫描write_posalloc_pos之间的槽,直到它找到一个空闲槽并设法用 CAS 保留它。

To pop, the reader first increments read_pos past all the items written at lower positions that it can see are consumed.为了弹出,读取器首先将read_pos递增到它可以看到已消耗的所有写入较低位置的项目。 Again this is an assist -- completing previous reads.这又是一个帮助——完成以前的阅读。 Then it scans from read_pos to alloc_pos to see if it can find an item that is finished being written.然后它从read_pos扫描到alloc_pos以查看是否可以找到已完成写入的项目。

As mentioned in comments, doing this for real gets annoying, with implementation decisions trading off performance against which ordering and availability guarantees you need, as well as jumping through hoops to prevent ABA problems.正如评论中提到的那样,真正这样做会很烦人,实施决策会根据您需要的订购和可用性保证来权衡性能,以及跳过箍以防止 ABA 问题。

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

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