我有以下问题定义:
使用以下操作设计无锁的简单链接列表:
下面显示了到目前为止实现的代码:
public class List<T>
{
private readonly T _sentinel;
private readonly Node<T> _head;
public List()
{
_head = new Node<T>();
_sentinel = default(T);
}
public List(T item)
{
_head = new Node<T>(item);
_sentinel = item;
}
public void Add(T item)
{
Node<T> node = new Node<T>(item);
do
{
node.Next = _head.Next;
}
while (!Atomic.CAS(ref _head.Next, node.Next, node));
}
public void Remove(Node<T> item)
{
Node<T> next;
Node<T> oldItem = item;
if (item.Value.Equals(_sentinel))
return;
item.Value = _sentinel;
do
{
next = item.Next;
if (next == null)
{
Atomic.CAS(ref item.Next, null, null);
return;
}
} while (!Atomic.CAS(ref item.Next, next, next.Next));
item.Value = next.Value;
}
}
头部实际上是一个易于使用的虚拟(哨兵)节点。实际的头部实际上是_head.Next。 尝试删除列表的最后一个元素时,问题在于删除操作:
在删除部分有两种情况:
所以我想做的是删除 C 节点:
问题是我只在一个对象上有一个CAS。 我怎样才能原子地解决这个问题呢?或者有没有更好的方法来执行我在这里错过的删除操作?
答案 0 :(得分:1)
当删除B时,我们通过将C&C的内容复制到B并删除C:B.Next = C.Next(在循环中)和B.Value = C.Value移动成功后做了一个技巧
因此您需要以原子方式修改两个内存位置。 .NET中的CAS不支持这一点。但是,您可以将这两个值包装在另一个可以原子换出的对象中:
class ValuePlusNext<T> {
T Value;
Node<T> Next;
}
class Node<T> {
ValuePlusNext<T> Value;
}
现在,您可以在一个原子操作中写入两个值。 CAS(ref Value, new ValuePlusNext<T>(next.Value, next.Value.Next)
。这样的事情。
奇怪的是,ValuePlusNext具有与旧Node类相同的结构。从某种意义上说,您现在正在为每个逻辑链路节点管理两个物理链接列表节点。
while (true) {
var old = item.Value;
var new = new ValuePlusNext(...);
if (CAS(ref Value, old, new)) break;
}