无锁名单删除

时间:2019-06-20 15:40:42

标签: c++ algorithm containers atomic lock-free

我一直在尝试实现无锁的slist擦除操作,但是显然存在问题。不幸的是,我真的很需要它。

为解决常见的与ABA cmpxchg相关的问题,我编写了agged_ptr <>“智能指针”类,该类将一个计数器嵌入到存储在std :: atomic <>中的指针。每次通过列表中的CAS更新指针时,标记值都会增加: newptr存储old,标记从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. } } 开始递增。 这允许多写入器事务,但不能解决同时更新两个指针的问题。 (例如,使用agged_ptr <>轻松实现堆栈)

请参见代码here。 在第256行是擦除()函数:

{{1}}

在测试代码中,两个线程同时将节点推送到列表中,两个线程通过数据搜索随机节点,然后尝试将其擦除。 我遇到的错误是:

  • 列表以某种方式变为循环状态(列表以null终止),因此线程永远卡住,反复访问列表,永远找不到列表的结尾。

1 个答案:

答案 0 :(得分:1)

对于您的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)中的布尔测试将返回true如果是“ void”。我猜这个名字应该是 invalid (无效),而不是 void (空),如果在布尔运算符中,它应该返回false(不正确)。如果itr变成“ void”(据我所知,在上面的代码中可能),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时,下一步是成为“ void”,并且从头开始重新执行迭代并不能解决任何问题,它会给出相同的结果结果,该列表已损坏。