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
- 该值只写入一次。这意味着
如果值不为null,则不需要刷新该值GetValue()
- 如果一个完整的内存障碍可以避免,那么它(更好)了
- 我知道无锁并发更好,但我不确定这里是否是这样
- 使用非易失性字段
- 使用
写入字段联锁。比较交换
- 使用
从字段读取联锁。比较交换
- 这取决于(可能是错误的)假设,即在对字段执行
后,下一次访问将获得至少与联锁.CompareExchange(ref v,null,null)
saw相同的最新值联锁.CompareExchange
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
仅在初始化后写入一次。它被初始化为<代码