我一直在尝试实现无锁的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}}
在测试代码中,两个线程同时将节点推送到列表中,两个线程通过数据搜索随机节点,然后尝试将其擦除。 我遇到的错误是:
答案 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”,并且从头开始重新执行迭代并不能解决任何问题,它会给出相同的结果结果,该列表已损坏。