在多线程 计算中,ABA问题发生在同步期间,当一个位置被读取两次,两个读取具有相同的值,并且“值相同”用于指示“没有任何改变”。但是,另一个线程可以在两个读取之间执行并更改值,执行其他工作,然后更改该值,从而欺骗第一个线程思考“没有任何改变”,即使第二个线程的工作违反了该假设。
当访问共享数据的多个线程(或进程)交错时,会发生ABA问题。以下是导致ABA问题的事件序列:
- 处理 {\ displaystyle P_ {1}}
从共享内存读取值A,
- {\ displaystyle P_ {1}}
被抢占,允许进程{\ displaystyle P_ {2}}
跑步,
- {\ displaystyle P_ {2}}
在抢占之前将共享内存值A修改为值B并返回到A,
- {\ displaystyle P_ {1}}
再次开始执行,看到共享内存值没有改变并继续。
虽然 {\ displaystyle P_ {1}} 可以继续执行,由于共享内存中的“隐藏”修改,行为可能不正确。
在实现无锁数据结构时遇到ABA问题的常见情况。如果从列表中删除项目,删除,然后分配新项目并将其添加到列表中,由于优化,分配的对象通常与删除的对象位于同一位置。因此,指向新项目的指针有时等于指向旧项目的指针,这是ABA问题。
解决方法
标记状态参考
常见的解决方法是在正在考虑的数量上添加额外的“标记”或“标记”位。例如,对指针使用比较和交换的算法可能会使用地址的低位来指示指针成功修改的次数。因此,即使地址相同,下一个比较和交换也会失败,因为标记位不匹配。这有时被称为ABA',因为第二个A与第一个A略有不同。这种标记的状态引用也用在事务存储器中。
如果“tag”字段包裹起来,则对ABA的保证不再存在。但是,已经观察到,在当前存在的CPU上,并且使用60位标签,只要程序寿命(即,不重新启动程序)限制为10年,就不可能进行环绕; 此外,有人认为,出于实际目的,通常有40-48位标签可以保证不会缠绕。由于现代CPU(特别是所有现代x64 CPU)倾向于支持128位CAS操作,因此在某些使用情况下,它允许在使用标记时提供针对ABA的坚定保证。[1]
中间节点
一种正确但昂贵的方法是使用非数据元素的中间节点,从而在插入和移除元素时确保不变量[Valois]延期回收[ 编辑]
另一种方法是推迟回收已删除的数据元素。推迟回收的一种方法是在具有自动垃圾收集器的环境中运行算法; 然而,这里的一个问题是,如果GC不是无锁的,那么整个系统不是无锁的,即使数据结构本身也是如此。
推迟回收的另一种方法是使用一个或多个危险指针,它们是指向无法在列表中出现的位置的指针。每个危险指针代表正在进行的变化的中间状态; 指针的存在确保了进一步的同步[Doug Lea]。危险指针是无锁的,但最多只能追踪两个元素才能使用。
延迟回收的另一种方法是使用读取副本更新(RCU),其涉及将更新封装在RCU读取侧关键部分中,然后在释放任何移除的数据元素之前等待RCU宽限期。以这种方式使用RCU可确保在所有当前正在执行的操作完成之前,所有已删除的数据元素都不会重新出现。RCU是无锁的,但不适合所有工作负载。
一些体系结构提供“更大”的原子操作,例如,双链表中的前向和后向链接都可以原子方式更新; 虽然此功能依赖于体系结构,但它特别适用于x86 / x64体系结构(x86允许使用64位CAS,所有现代x64 CPU都允许使用128位CAS)。
一些体系结构提供了加载链接的存储条件指令,其中仅当没有指示位置的其他存储时才执行存储。这有效地将“存储包含值”的概念与“存储已被更改”区分开来。示例包括DEC Alpha,MIPS,PowerPC和ARM(v6及更高版本)。
251

被折叠的 条评论
为什么被折叠?



