更新:
就我而言,“另一个线程可能会删除然后重新插入第 4 行和第 4b 行之间的节点”意味着节点可以被删除、修改并重新插入到同一位置,然后再被另一个线程完全回收。第 4 行和第 4b 行,导致潜在的 ABA 问题,即节点的内容发生变化,即使其地址保持不变。
我正在阅读 Maged M. Michael 的一篇论文,其中介绍了危险指针。
这是一个使用危险指针的入队函数:
//FIFO queue algorithm, stripped of memory management code
structure NodeType { Data:DataType; Next:*NodeType; }
//Shared variables
Head,Tail:*NodeType;
//Initially both Head and Tail point to a dummy node
//hp0 is private ptr to 2 of the thread's hazard ptrs
Enqueue(data: DataType) {
1. node <- NewNode();
2. node.Data <- data;
3. node.Next <- null;
while true {
4. t <- Tail;
4a: *hp0 <- t;
4b: if (Tail ≠ t) continue;
5. next <- t.Next
6. if (Tail ≠ t) continue;
7. if (next ≠ null) {CAS(&Tail, t, next); continue;}
8. if CAS(&t.next, null, node) break;
}
9. CAS(&Tail, t, node);
}
如果没有第 4a 行和第 4b 行,访问第 5 行的节点可能会存在访问危险,因为在当前线程执行第 4 行之后,另一个线程可能已删除并回收了该节点。
访问危险:一个线程尝试访问已被另一个线程释放的节点。作者说
第4a和4b行保证了安全的内存回收和ABA预防。
作者还提到另一个线程可能会删除然后重新插入第 4 行和第 4b 行之间的节点。然而,作者说这是可以接受的,因为它不违反第 3.3 节中的条件。
换句话说,作者承认可能会发生 ABA 问题(节点删除和重新插入),但只要满足 3.3 节中的特定条件,就可以了。
第 3.3 节条件:
因此,关联算法必须满足的条件是,每当一个线程持有对某个节点的危险引用时,必须至少有一个线程的危险指针从该节点开始持续持有该引用。节点对于线程来说绝对是安全的。请注意,这种情况意味着在节点退出时,没有线程可以创建对节点的新的危险引用。
我不明白这种情况与防止第 4a 和 4b 行的 ABA 问题有什么关系。
您的问题中没有包含
*t
操作的代码,所以这里是:
Deqeue
ABA 问题依赖于地址的重用,因此本质上以前属于队列一部分的节点必须被重用并重新插入队列中而其他线程尚未意识到它已被删除
。在这种情况下,线程会错误地认为它仍然看到正确且未更改的状态。内存回收算法可以防止这种情况发生,因为在回收之前没有节点可以被重用。通过在 while true {
11: h <- Head;
11a: *hp0 <- h;
11b: if (Head != h) continue;
12: t <- Tail
13: next <- h.Next
13a: *hp1 <- next
14: if (Head != h) continue;
15: if (next == null) return EMPTY;
16: if (h == t) { CAS(&Tail, t, next); continue; }
17: data <- next.Data;
18: if (CAS(&Head, h, next) break;
}
19: RetireNode(h); return data;
}
中的第19行中退休节点,我们将其标记为准备回收,但只有当前可能看到它的所有线程(即,有一个引用它的危险指针)完成其操作后,它才会被回收。并且只要节点没有被回收,它的内存就无法被
Dequeue
重用。因此,这可以通过延迟节点的重用直到安全的时候来防止 ABA 问题。