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

时间:2015-03-27 18:01:21

标签: c# .net concurrency .net-4.5 versioning

假设我们有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并返回bool而不是object;
  2. TryPopRange让我们创建了所有以前版本大小的附加数组。

1 个答案:

答案 0 :(得分:2)

这不是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只能执行线程安全的优化)。

如果传递给Messages的{​​{1}}对象在最新状态下可以更改,那么您所要做的就是先复制一份。 SetLatest成为:

SetLatest

如果允许读者更改返回的public void SetLatest(Messages m) { _current = DeepClone(m); } 对象,则必须先复制它,然后再让它们使用。 Messages成为:

ReadLatest

请注意,如果public Messages ReadLatest() { return DeepClone(_current); } byte[]字段中包含的值在每个邮件的生命周期内都是不可变的,那么您只需要一个浅表副本,而不是深层副本。


通过将界面包装在一个简单的属性中,您可以使界面更加出色:

Messages

如果实际确实有POD类型(例如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() }; } } ),那么我建议最简单的解决方案是将它包装在一个类中,这样你就可以有一个原子引用它的副本,这将允许您使用上面的解决方案。想到struct Messages

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

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