原文:https://www.modernescpp.com/index.php/c-core-guidelines-the-rules-to-lock-free-programming
今天,我从上一篇帖子中解决了这个难题。多亏了我的读者,对ABA问题的分析非常准确。

仅提醒您。C ++核心准则中的规则CP.100是谜语的起点。
规则中的挑战指出,以下代码段存在错误。该错误应归因于ABA问题。后ABA -A——一个是不一样的A给出了一个简明的介绍到ABA问题。
extern atomic<Link*> head; // the shared head of a linked list
Link* nh = new Link(data, nullptr); // make a link ready for insertion
Link* h = head.load(); // read the shared head of the list
do {
if (h->data <= data) break; // if so, insert elsewhere
nh->next = h; // next element is the previous head
} while (!head.compare_exchange_weak(h, nh)); // write nh to head or to h
特别感谢我的德语博客的匿名读者,这是一段可运行的代码,并对问题进行了深入分析。
#include <atomic>
class Link {
public:
Link(int d, Link* p) : data(d), next(p) {}
int data;
Link* next;
};
void foo (int data) {
extern std::atomic<Link*> head;
Link* nh = new Link(data, nullptr); // (1)
Link* h = head.load(); // (2)
do {
if (h->data <= data) break; // (3)
nh->next = h; // (4)
} while (!head.compare_exchange_weak(h, nh)); // (5)
}
首先,这段代码应该做什么?它创建一个单链接节点列表(Link)。每个节点都有一个指针和一个数据字段。该指针指向的下一个元素(节点 - >下),并且将数据字段存储的值:节点- >数据。每个新节点都以数据升序排序的方式插入到单链列表中。
要将新节点插入到单链接列表中的正确位置,请执行以下步骤。
第1行:创建了一个新节点。这很好,因为该节点是在每个线程中本地创建的。
第2行:读取指向头部的指针。读取操作是原子的;因此,单独考虑操作也可以。孤立意味着什么?第2行与第5行创建了一种交易。第2行存储事务的初始状态,如果两者之间没有任何变化,则第5行发布事务。
第3行:与前面的行相对应,此第3行没有问题。如果头的数据小于新数据,则仅进行值比较,该比较可能会终止功能。
第4行:nh是本地数据;因此,nh-> next的赋值很好。同时可能更改了head h,因此,之后nh-> next不再引用head。仅当更改在下一行5中提交时,这才是问题。
第5行:指令head.compare_exchange_weak(h,nh)将head与第2行中存储的h进行比较,并在原子步骤中以相同的方式交换h和nh。如果head不等于h,则将h设置为head。第5行是原子事务的结尾,并发布了更新的单链表。
这几行代码有什么问题?整个事务基于第5行中的指针比较。如果可以愚弄指针比较,则可以断开单链表。
在加载磁头(第2行)和检查当前磁头是否是旧磁头(第5行)之间存在一个时间窗口。该装置的另一个线程可踢,在此期间的变化头,但第一个线程是没有意识到这一点。
让我介绍一个多虫的事件序列。
不变式的破坏
以下单个链接列表的不变之处在于,数据以升序排列。蓝色节点是列表的开头。
这是列表的初始结构。头的地址为0x0815。

线程1

想要添加带有数据42的新节点。
42 <47,因此新节点应成为新头。
在第(5)行之前,线程2插入。
线程2
卸下当前磁头47。
使具有数据的节点60到达新的头。

想要添加数据30的新节点。

使30成为地址为0x0815的新头部; 这是以前的地址47,由于内存重用而经常发生。

线程1
将具有数据的节点42移到新的头;这很好,因为第5行中的比较只是将旧节点与新节点进行比较,并且它们具有相同的地址:0x0815。

现在,单链列表已损坏,因为节点的值未按升序排序。