无锁数据结构中需要多少个 ABA 标记位?

Posted

技术标签:

【中文标题】无锁数据结构中需要多少个 ABA 标记位?【英文标题】:How many ABA tag bits are needed in lock-free data structures? 【发布时间】:2017-07-19 18:24:49 【问题描述】:

无锁数据结构中 ABA 问题的一种流行解决方案是使用额外的单调递增标记来标记指针。

 struct aba 
      void *ptr;
      uint32_t tag;
 ;

但是,这种方法有一个问题。它真的很慢并且有很大的缓存问题。如果我放弃标签字段,我可以获得两倍的加速。但这不安全吗?

所以我下一次尝试 64 位平台的东西是在 ptr 字段中填充位。

struct aba 
    uintptr __ptr;
;
uint32_t get_tag(struct aba aba)  return aba.__ptr >> 48U; 

但是有人对我说,标签只有 16 位是不安全的。我的新计划是使用缓存行指针对齐来填充更多标记位,但我想知道这是否可行。

如果这不起作用,我的下一个计划是使用 Linux 的 MAP_32BIT mmap 标志来分配数据,因此我只需要 32 位指针空间。

无锁数据结构中的 ABA 标签需要多少位?

【问题讨论】:

我知道你从一个单调递增的标签分配策略开始,我承认我对这个问题了解不多,但总的来说,不会有一个廉价的类似哈希的函数(比如,hyperlog -distributed numeric buckets) 保证标签不冲突? @bright-star 我自己正在考虑使用散列函数,但我无法构造一个好的论据来使用一个而不是仅仅增加标签。不过,这似乎是一个非常有趣的想法。 【参考方案1】:

实际安全的标签位数可以根据抢占时间和指针修改的频率来估计。

提醒一下,ABA问题发生在线程读取它想要通过比较和交换更改的值时,被抢占,当它恢复时,指针的实际值恰好等于线程之前读取的值.因此,尽管其他线程可能在抢占时间内进行了数据结构修改,但比较和交换操作可能会成功。

添加单调递增标记的想法是使指针的每次修改都是唯一的。为了成功,增量必须在修改线程可能被抢占期间产生唯一的标记值;即,为了保证正确性,标签可能不会在整个抢占时间内环绕。

让我们假设抢占会持续一个操作系统调度时间片,通常是几十到几百毫秒。 CAS 在现代系统上的延迟是几十到几百纳秒。如此粗略的最坏情况估计是,当线程被抢占时,可能会有数百万个指针修改,因此标签中应该有 20+ 位以使其不回绕。

在实践中,可以根据已知的 CAS 操作频率对特定的实际用例做出更好的估计。还需要更准确地估计最坏情况下的抢占时间;例如,被高优先级作业抢占的低优先级线程可能会以更长的抢占时间结束。

【讨论】:

在主观上,我发现将备用地址位用于标记值的方法非常脆弱,而且相当不可移植且不能面向未来(例如,如果未来的处理器世代将使用超过 48位用于内存寻址) - 因此在实际使用中很危险。【参考方案2】:

根据论文

http://web.cecs.pdx.edu/~walpole/class/cs510/papers/11.pdf 危险指针:无锁对象的安全内存回收(IEEE TRANSACTIONS ON PARALLEL AND DISTRIBUTED SYSTEMS,第 15 卷,第 6 期,2004 年 6 月,第 491 页)作者:PhD Maged M. Michael

标签位的大小应该使在真正的无锁场景中无法进行环绕(我可以这样理解,就好像您可能有 N 个线程正在运行并且每个线程都可以访问该结构,您至少应该有 N+1 个不同的标签状态):

6.1.1 IBM ABA 预防标签

节点复用最早最简单的无锁方法是 引入的标签(更新计数器)方法 IBM System 370 [11] 上的 CAS 文档。它 需要将标签与每个位置相关联 ABA倾向比较操作的目标。通过递增 关联位置的值为时的标签 书面的,比较操作(例如,CAS)可以确定是否 该位置自上次访问后被写入 相同的线程,从而防止 ABA 问题。 该方法要求标签包含足够的位以使 在任何执行期间不可能完全环绕 单次无锁尝试。这种方法非常有效并且 允许立即重用退役节点。

【讨论】:

不能保证另一个线程只能修改一次值,所以我相信任何基于线程数的限制都是不安全的。 论文列出了条件。你还有其他不同情况的论文吗? 所述条件是正确的,但我的解释不同。特别是,“任何单个无锁尝试的执行”包括线程被抢占的时间,而其他线程在此期间可能执行的操作远不止单个操作。我已经编写了一个答案来澄清这一点。【参考方案3】:

根据您的数据结构,您可以从指针中窃取一些额外的位。例如,如果对象是 64 字节并且始终在 64 字节边界上对齐,则每个指针的低 6 位可用于标记(但这可能是您已经为新计划建议的内容)。

另一种选择是在对象中使用索引而不是指针。

在连续对象的情况下,当然只是简单地成为数组或向量的索引。对于在堆上分配对象的列表或树,您可以使用自定义分配器并在分配的块中使用索引。

对于 1700 万个对象,您只需要 24 位,为标签留下 40 位。

这需要一些(小而快)额外的计算来获得地址,但如果对齐是 2 的幂,则只需要移位和加法。

【讨论】:

以上是关于无锁数据结构中需要多少个 ABA 标记位?的主要内容,如果未能解决你的问题,请参考以下文章

是否存在多个读取或写入线程的无锁队列?

使用 Load-link/store-conditional 来防止 ABA 的无锁 C++11 示例?

无锁编程:lock-free原理;CAS;ABA问题

多线程编程之无锁队列

UDP头部8字节,是怎么个说法

简单的无锁堆栈c ++ 11