C ++核心准则:解决难题

原文:https://www.modernescpp.com/index.php/c-core-guidelines-the-rules-to-lock-free-programming

今天,我从一篇帖子中解决了这个难题。多亏了我的读者,对ABA问题的分析非常准确。


仅提醒您。C ++核心准则中的规则CP.100是谜语的起点。

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。

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

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容