【发布时间】:2019-06-20 15:40:42
【问题描述】:
我一直在尝试实现无锁 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.
}
}
在测试代码中,两个线程同时将节点推入列表,两个线程通过数据搜索随机节点并尝试擦除它们。 我遇到的错误是:
- 列表以某种方式变为循环(列表以空值终止),因此线程永远卡住了,迭代列表永远找不到列表的结尾。
【问题讨论】:
-
不确定我是否正确理解了
tagged_ptr代码。假设您使用的是 32 位 CPU,那么tag_type是uint32,因此ptr和tag在内存中的同一位置重叠(它们是相同的,因为填充是 0 字节)。跨度> -
如果你在 64 位 CPU 上,那么
tag_type是uint16,ptr的低 16 位用于存储tag。 (这对我来说似乎是错误的)。据我记得,在 32 位机器上使用低 4 位应该是安全的,在 64 位机器上使用高 16 位(不使用从 48 到 64) -
你有一些代码来测试你的
tagged_ptr实现吗? -
请查看this
-
我已经测试了 tagged_ptr 类并且它运行正常。在 x86_64 上有一个技巧,我可以使用 cmpxchg8b 而不是 cmpxchg16b(在具有 48 位虚拟地址空间的系统上)逃脱,因为不使用高 16 位地址。 (我知道这有点不稳定!)我已经成功地使用这个类在 RPi2 上运行了一个无锁堆栈。如果 sizeof(void*) == 4 那么 sizeof(tagged_ptr
) == 8,如果 sizeof(void*) == 8 那么 sizeof(tagged_ptr ) == 8 并且标签存储在未使用的高 16 -地址的位。
标签: c++ algorithm containers atomic lock-free