C# 从联锁变量中读取最新值,仅对变量进行一次写入

C# 从联锁变量中读取最新值,仅对变量进行一次写入,c#,volatile,lock-free,interlocked,C#,Volatile,Lock Free,Interlocked,我想用两种方法创建一个类: void SetValue(T value)存储一个值,但只允许存储一个值(否则会引发异常) T GetValue()检索该值(如果还没有值,则抛出异常) 我有以下愿望/限制: 读取价值应该很便宜 写入值可能(适度)昂贵 GetValue()仅当缺少最新值时才应引发异常(null):在另一个线程中调用SetValue()后,它不应基于过时的null值引发异常 该值只写入一次。这意味着GetValue()如果值不为null,则不需要刷新该值 如果一个完整的内存障碍

我想用两种方法创建一个类:

  • void SetValue(T value)
    存储一个值,但只允许存储一个值(否则会引发异常)
  • T GetValue()
    检索该值(如果还没有值,则抛出异常)
我有以下愿望/限制:

  • 读取价值应该很便宜
  • 写入值可能(适度)昂贵
  • GetValue()
    仅当缺少最新值时才应引发异常(
    null
    ):在另一个线程中调用
    SetValue()
    后,它不应基于过时的
    null
    值引发异常
  • 该值只写入一次。这意味着
    GetValue()
    如果值不为null,则不需要刷新该值
  • 如果一个完整的内存障碍可以避免,那么它(更好)了
  • 我知道无锁并发更好,但我不确定这里是否是这样
我想出了几种方法来实现这一点,但我不确定哪些是正确的,哪些是有效的,为什么它们是正确的和有效的,以及是否有更好的方法来实现我想要的

方法1
  • 使用非易失性字段
  • 使用
    联锁。比较交换
    写入字段
  • 使用
    联锁。比较交换
    从字段读取
  • 这取决于(可能是错误的)假设,即在对字段执行
    联锁.CompareExchange(ref v,null,null)
    后,下一次访问将获得至少与
    联锁.CompareExchange
    saw相同的最新值
守则:

public class SetOnce1<T> where T : class
{
    private T _value = null;

    public T GetValue() {
        if (_value == null) {
            // Maybe we got a stale value (from the cache or compiler optimization).
            // Read an up-to-date value of that variable
            Interlocked.CompareExchange<T>(ref _value, null, null);
            // _value contains up-to-date data, because of the Interlocked.CompareExchange call above.
            if (_value == null) {
                throw new System.Exception("Value not yet present.");
            }
        }

        // _value contains up-to-date data here too.
        return _value;
    }

    public T SetValue(T newValue) {
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }

        if (Interlocked.CompareExchange<T>(ref _value, newValue, null) != null) {
            throw new System.Exception("Value already present.");
        }

        return newValue;
    }
}
public class SetOnce2<T> where T : class
{
    private volatile T _value = null;

    public T GetValue() {
        if (_value == null) {
            throw new System.Exception("Value not yet present.");
        }
        return _value;
    }

    public T SetValue(T newValue) {
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }

        #pragma warning disable 0420
        T oldValue = Interlocked.CompareExchange<T>(ref _value, newValue, null);
        #pragma warning restore 0420

        if (oldValue != null) {
            throw new System.Exception("Value already present.");
        }
        return newValue;
    }
}
public class SetOnce3<T> where T : class
{
    private T _value = null;

    public T GetValue() {
        if (_value == null) {
            // Maybe we got a stale value (from the cache or compiler optimization).
            lock (this) {
                // Read an up-to-date value of that variable
                if (_value == null) {
                    throw new System.Exception("Value not yet present.");
                }
                return _value;
            }
        }
        return _value;
    }

    public T SetValue(T newValue) {
        lock (this) {
            if (newValue == null) {
                throw new System.ArgumentNullException();
            }

            if (_value != null) {
                throw new System.Exception("Value already present.");
            }

            _value = newValue;

            return newValue;
        }
    }
}
public class SetOnce4<T> where T : class
{
    private volatile T _value = null;

    public T GetValue() {
        if (_value == null) {
            throw new System.Exception("Value not yet present.");
        }
        return _value;
    }

    public T SetValue(T newValue) {
        lock (this) {
            if (newValue == null) {
                throw new System.ArgumentNullException();
            }

            if (_value != null) {
                throw new System.Exception("Value already present.");
            }

            _value = newValue;

            return newValue;
        }
    }
}
方法3
  • 使用非易失性字段
  • 书写时使用锁
  • 如果读取null,则在读取时使用锁(因为引用是原子读取的,所以我们将在锁之外获得一致的值)
守则:

public class SetOnce1<T> where T : class
{
    private T _value = null;

    public T GetValue() {
        if (_value == null) {
            // Maybe we got a stale value (from the cache or compiler optimization).
            // Read an up-to-date value of that variable
            Interlocked.CompareExchange<T>(ref _value, null, null);
            // _value contains up-to-date data, because of the Interlocked.CompareExchange call above.
            if (_value == null) {
                throw new System.Exception("Value not yet present.");
            }
        }

        // _value contains up-to-date data here too.
        return _value;
    }

    public T SetValue(T newValue) {
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }

        if (Interlocked.CompareExchange<T>(ref _value, newValue, null) != null) {
            throw new System.Exception("Value already present.");
        }

        return newValue;
    }
}
public class SetOnce2<T> where T : class
{
    private volatile T _value = null;

    public T GetValue() {
        if (_value == null) {
            throw new System.Exception("Value not yet present.");
        }
        return _value;
    }

    public T SetValue(T newValue) {
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }

        #pragma warning disable 0420
        T oldValue = Interlocked.CompareExchange<T>(ref _value, newValue, null);
        #pragma warning restore 0420

        if (oldValue != null) {
            throw new System.Exception("Value already present.");
        }
        return newValue;
    }
}
public class SetOnce3<T> where T : class
{
    private T _value = null;

    public T GetValue() {
        if (_value == null) {
            // Maybe we got a stale value (from the cache or compiler optimization).
            lock (this) {
                // Read an up-to-date value of that variable
                if (_value == null) {
                    throw new System.Exception("Value not yet present.");
                }
                return _value;
            }
        }
        return _value;
    }

    public T SetValue(T newValue) {
        lock (this) {
            if (newValue == null) {
                throw new System.ArgumentNullException();
            }

            if (_value != null) {
                throw new System.Exception("Value already present.");
            }

            _value = newValue;

            return newValue;
        }
    }
}
public class SetOnce4<T> where T : class
{
    private volatile T _value = null;

    public T GetValue() {
        if (_value == null) {
            throw new System.Exception("Value not yet present.");
        }
        return _value;
    }

    public T SetValue(T newValue) {
        lock (this) {
            if (newValue == null) {
                throw new System.ArgumentNullException();
            }

            if (_value != null) {
                throw new System.Exception("Value already present.");
            }

            _value = newValue;

            return newValue;
        }
    }
}
public class SetOnce3其中T:class
{
私有T_值=null;
公共T GetValue(){
如果(_值==null){
//也许我们得到了一个过时的值(来自缓存或编译器优化)。
锁(这个){
//读取该变量的最新值
如果(_值==null){
抛出新的System.Exception(“值尚未出现”);
}
返回_值;
}
}
返回_值;
}
公共T设置值(T新值){
锁(这个){
if(newValue==null){
抛出新的System.ArgumentNullException();
}
如果(_值!=null){
抛出新的System.Exception(“值已存在”);
}
_值=新值;
返回新值;
}
}
}
方法4
  • 使用易失性字段
  • 使用锁写入值
  • 直接读取值,因为字段是可变的(即使我们不使用锁,也会得到一致的值,因为引用是原子读取的)
守则:

public class SetOnce1<T> where T : class
{
    private T _value = null;

    public T GetValue() {
        if (_value == null) {
            // Maybe we got a stale value (from the cache or compiler optimization).
            // Read an up-to-date value of that variable
            Interlocked.CompareExchange<T>(ref _value, null, null);
            // _value contains up-to-date data, because of the Interlocked.CompareExchange call above.
            if (_value == null) {
                throw new System.Exception("Value not yet present.");
            }
        }

        // _value contains up-to-date data here too.
        return _value;
    }

    public T SetValue(T newValue) {
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }

        if (Interlocked.CompareExchange<T>(ref _value, newValue, null) != null) {
            throw new System.Exception("Value already present.");
        }

        return newValue;
    }
}
public class SetOnce2<T> where T : class
{
    private volatile T _value = null;

    public T GetValue() {
        if (_value == null) {
            throw new System.Exception("Value not yet present.");
        }
        return _value;
    }

    public T SetValue(T newValue) {
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }

        #pragma warning disable 0420
        T oldValue = Interlocked.CompareExchange<T>(ref _value, newValue, null);
        #pragma warning restore 0420

        if (oldValue != null) {
            throw new System.Exception("Value already present.");
        }
        return newValue;
    }
}
public class SetOnce3<T> where T : class
{
    private T _value = null;

    public T GetValue() {
        if (_value == null) {
            // Maybe we got a stale value (from the cache or compiler optimization).
            lock (this) {
                // Read an up-to-date value of that variable
                if (_value == null) {
                    throw new System.Exception("Value not yet present.");
                }
                return _value;
            }
        }
        return _value;
    }

    public T SetValue(T newValue) {
        lock (this) {
            if (newValue == null) {
                throw new System.ArgumentNullException();
            }

            if (_value != null) {
                throw new System.Exception("Value already present.");
            }

            _value = newValue;

            return newValue;
        }
    }
}
public class SetOnce4<T> where T : class
{
    private volatile T _value = null;

    public T GetValue() {
        if (_value == null) {
            throw new System.Exception("Value not yet present.");
        }
        return _value;
    }

    public T SetValue(T newValue) {
        lock (this) {
            if (newValue == null) {
                throw new System.ArgumentNullException();
            }

            if (_value != null) {
                throw new System.Exception("Value already present.");
            }

            _value = newValue;

            return newValue;
        }
    }
}
public class SetOnce4其中T:class
{
私有易失性T_值=null;
公共T GetValue(){
如果(_值==null){
抛出新的System.Exception(“值尚未出现”);
}
返回_值;
}
公共T设置值(T新值){
锁(这个){
if(newValue==null){
抛出新的System.ArgumentNullException();
}
如果(_值!=null){
抛出新的System.Exception(“值已存在”);
}
_值=新值;
返回新值;
}
}
}
其他方法
我还可以结合任何书写技巧来读取值。

除了带有
锁的方法(#3)外,您的任何方法都不能正常工作

看:

如果不在
lock
内,则此代码不是原子代码,也不是线程安全的。其他线程仍可能在
if
return
之间将
\u值设置为
null
。您可以做的是设置为局部变量:

    var localValue = _value;
    if (localValue == null) {
        throw new System.Exception("Value not yet present.");
    }
    return localValue;
但它仍然可以返回失速值。您最好使用
lock
-清晰、简单、快速

编辑:避免使用
锁定(此)
,因为
在外部可见,并且第三方代码可能决定在对象上锁定

编辑2:如果无法设置空值,则只需执行以下操作:

public T GetValue() {
    if (_value == null) {
        throw new System.Exception("Value not yet present.");
    }
    return _value;
}

public T SetValue(T newValue) {
    lock (writeLock)
    {        
        if (newValue == null) {
            throw new System.ArgumentNullException();
        }
        _value = newValue;
        return newValue;
    }
}

嗯,不确定波动性,但如果你不介意有点滥用和调用第二种方法。。。(而且它不依赖于可空性;可自由用于值类型)也避免了getter中的空检查。在写操作上只进行了锁定,因此,好吧,唯一的负面影响来自在获取值时调用委托

public class SetOnce<T>
{
    private static readonly Func<T> NoValueSetError = () => { throw new Exception("Value not yet present.");};

    private Func<T> ValueGetter = NoValueSetError;
    private readonly object SetterLock = new object();

    public T SetValue(T newValue)
    {
        lock (SetterLock)
        {
            if (ValueGetter != NoValueSetError)
                throw new Exception("Value already present.");
            else
                ValueGetter = () => newValue;
        }

        return newValue;
    }

    public T GetValue()
    {
        return ValueGetter();
    }
}

您是否考虑过使用
读写器锁定(Slim)
?您确定简单的
不能满足您的需要吗?为什么不呢?
SetOnce
类的使用模式是什么?我忘记了
ReaderWriterLock
,我会给它一个锁。但就像常规的
锁一样,我认为它会在每次读取时引入同步开销,我宁愿避免这种情况,因为值只更改一次,因此在读取非空值(无锁)时,可以保证它不会过时。ReaderWriterLock(Slim)确实比顺序锁好,由于它允许多个同时读取。请注意,使用此作为锁定目标是一个坏主意(lock(this)),因此声明单独的私有对象字段更安全。否,另一个不能将
\u value
设置为null:我的情况很特殊,因为
\u value
仅在初始化后写入一次。它被初始化为<代码