如何使整个 POD 对象读取和更新操作无锁?

How to make whole POD object read and update operations lockfree?

假设我们有一个 POD 类型:

    private class Messages {
        public byte[] last;
        public byte[] next;
    }

及其实例messages.

当用户(调用者)请求实例时,我们想给他消息对象的深层副本(可以不是最新的)。当用户设置自己的版本时,我们希望将其尽可能地提供给其他人,但又不中断读取请求(应删除旧版本,尽快不中断读取)。

如何使用 System.Collections.Concurrent 进行此类对象版本控制?

我尝试了什么:

    internal class CuncurrentMessagesHelper {
        private readonly ConcurrentStack<Messages> _stack = new ConcurrentStack<Messages>();
        public CuncurrentMessagesHelper() {
        }

        public void SetLatest(Messages m) {
            var length = _stack.Count;
            _stack.Push(m);
            var range = new Messages[length];
            _stack.TryPopRange(range, 0, length);
        }

        public bool ReadLatest(out Messages result) {
            return _stack.TryPeek(out result);
        }
    }

然而,这种帮助方法似乎很丑陋。

  1. 因为即使我们知道结果是有保证的,我们也使用 try 和 return bool 而不是 object;
  2. 它的 TryPopRange 使我们创建具有所有先前版本大小的附加数组。

这不是 POD。这是一个 POCO。我建议您仔细阅读 .NET 的值类型和引用类型之间的区别,因为它们的语义在编写安全的并发代码时至关重要。

由于 C# 引用保证是原子的,因此解决方案很简单(并且不需要任何特殊的并发容器)。

假设您的 Messages 对象在传入后是不可变的:

internal class ConcurrentMessagesHelper {
    private volatile Messages _current;

    public void SetLatest(Messages m) {
        _current = m;
    }

    public Messages ReadLatest() {
        return _current;
    }
}

请注意,它是对正在此处复制的对象的引用(原子地),而不是对象的 byte[] 字段。 volatile 是必需的,因为引用由多个线程访问(它确保正确的行为,特别是关于内存排序和限制 JIT 只能执行线程安全的优化)。

如果传递给 SetLatestMessages 对象可以更改,而它是最新的,那么您所要做的就是先制作一个副本。 SetLatest 变为:

public void SetLatest(Messages m) {
    _current = DeepClone(m);
}

如果允许读者更改返回的 Messages 对象,那么您必须在让他们也拥有之前复制它。 ReadLatest 变为:

public Messages ReadLatest() {
    return DeepClone(_current);
}

请注意,如果 Messagesbyte[] 字段中包含的值在每条消息的生命周期内都是不可变的,那么您只需要一个浅拷贝,而不是深拷贝。


您可以将界面包装在一个简单的 属性:

中,从而使界面更加美观
internal class ConcurrentMessagesHelper {
    private volatile Messages _current;
    public Messages Current {
        get { return DeepClone(_current); }
        set { _current = DeepClone(value); }
    }

    private static Messages DeepClone(Messages m)
    {
        if (m == null)
            return null;
        return new Messages {
            last = m.last == null ? null : (byte[])m.last.Clone(),
            next = m.next == null ? null : (byte[])m.next.Clone()
        };
    }
 }

如果您 实际上 确实有 POD 类型(例如 struct Messages),那么我建议最简单的解决方案是将其包装在 class 中所以你可以有一个原子引用它的副本,这将允许你使用上面的解决方案。 StrongBox<T> 想到了。

这种情况下的代码变得更加简单,因为不需要显式复制:

private struct Messages {
    public byte[] last;
    public byte[] next;
}

internal class ConcurrentMessagesHelper {
    private volatile StrongBox<Messages> _current;
    public Messages Current {
        get { return _current.Value; }
        set { _current = new StrongBox<Messages>(value); }
    }
}

如果 Messages 中的字节数组可以在对象的生命周期内发生变化,那么我们仍然需要深度克隆,但是:

internal class ConcurrentMessagesHelper {
    private volatile StrongBox<Messages> _current;
    public Messages Current {
        get { return DeepClone(_current.Value); }
        set { _current = new StrongBox<Messages>(DeepClone(value)); }
    }

    private static Messages DeepClone(Messages m)
    {
        return new Messages {
            last = m.last == null ? null : (byte[])m.last.Clone(),
            next = m.next == null ? null : (byte[])m.next.Clone()
        };
    }
}