gpt4 book ai didi

c++ - 无锁列表删除

转载 作者:塔克拉玛干 更新时间:2023-11-03 06:06:28 26 4
gpt4 key购买 nike

我一直在尝试实现一个无锁的 slist 删除操作,但我有明显的问题。不幸的是,我真的真的需要它。

为了解决常见的 ABA cmpxchg 相关问题,我编写了 tagged_ptr<>“智能指针”类,它嵌入了一个计数器,指向存储在 std::atomic<> 中的指针。每次通过列表中的 CAS 更新指针时,标记值都会递增:head.compare_exchange_weak(old, old(newptr)) 存储 newptr 以及来自 old 的递增标签。这允许多作者事务,但它没有解决同时更新两个指针的问题。 (例如,使用 tagged_ptr<> 很容易实现堆栈)

查看代码here .第 256 行是 erase() 函数:

bool erase(list_node * node) {
std::atomic<tagged_ptr<list_node>>* before;
tagged_ptr<list_node> itr, after;
for(;;) {
// Find previous (or head) before-node-ptr
before = &head;
itr = before->load(std::memory_order_acquire);
while(itr) {
if(itr.get() == node) {
break;
} else if(itr.is_void()) {
// Thread interfered iteration.
before = &head;
itr = before->load(std::memory_order_acquire);
} else {
// Access next ptr
before = &itr->next;
itr = before->load(std::memory_order_acquire);
}
}

after = node->next.load(std::memory_order_acquire);
if(after.is_void() || !itr) {
return false;
}

// Point before-ptr to after. (set head or previous node's next ptr)
if(before->compare_exchange_strong(itr, itr(after))) {
// Set node->next to invalid ptr.
// list iterators will see it and restart their operation.
while(!node->next.compare_exchange_weak(after, after().set_void()))
;
return true;
}
// If *before changed while trying to update it to after, retry search.
}
}

在测试代码中,两个线程同时将节点插入列表,两个线程通过数据搜索随机节点并尝试删除它们。我遇到的错误是:

  • 列表以某种方式变成循环的(列表以 null 终止),因此线程会永远卡住,迭代列表永远找不到列表的末尾。

最佳答案

我对您的tagged_ptr 实现有些疑问。另外,我对这部分代码有一些疑问:

         } else if(itr.is_void()) {
// Thread interfered iteration.
before = &head;
itr = before->load(std::memory_order_acquire);

假设一个线程删除了最后一个节点(您在列表中有 1 个节点并且两个线程都调用了删除)。剩下的线程会查询头指针,它是空的。这部分代码将进入无限循环,因为它在 while(itr) 循环中。

这部分也不是原子的:

            // Point before-ptr to after. (set head or previous node's next ptr)
if(before->compare_exchange_strong(itr, itr(after))) {
// Set node->next to invalid ptr.
// list iterators will see it and restart their operation.
while(!node->next.compare_exchange_weak(after, after().set_void()))
;
return true;
}

如果 before 被第一个 CAS 修改,你的 node 是一个独立的指针,仍然指向列表。另一个线程可以将其 before 设置为此 node 并修改它并返回。老实说,如果您的列表是循环的,那么调试起来并不难,只需在调试器下中断并按照列表进行操作即可。您会看到它何时循环,并且您可以弄清楚它是如何做到的。您也可以为此使用 valgrind。

tagged_ptr 类很难掌握,使用“set_void()”方法将内部 ptr 设置为 0xFF..F然而,如果 while(itr) 为“void”,则 bool 测试将返回 true。我猜名字应该是 invalid 而不是 void 并且它应该在 bool 运算符中返回 false 如果它是(不是真的)。如果 itr 变为“无效”(据我所知,在上面的代码中是可能的) while(itr) 将无限期循环。

例如,假设您有:

Head:A -> B -> C

然后在删除一些线程后你会得到

Thread 2 removing C : Head:A, before = &B on first iteration, exiting the while(itr) loop since itr == C (scheduled here)
Thread 1 removing B : Head:A->C and B->C (scheduled just before line 286 of your example)
Thread 2 resume, and will modify B to B->null (line 283)
and then C->null to C->yourVoid (line 286, then it's scheduled)

Then, thread 1 update B->next to B->yourVoid (useless here for understanding the issue)
You now have A->C->yourVoid

无论何时在这里迭代,你都会有一个无限循环,因为当 itr 搜索到达 C 时,下一步是“无效”并且从 head 重新开始迭代不会在这里解决任何问题,它会给出相同的结果,列表被破坏了。

关于c++ - 无锁列表删除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56689541/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com