.net lock()在这里有意义吗?
我有一个简单的类,如:.net lock()在这里有意义吗?,.net,multithreading,locking,atomic,interlocked,.net,Multithreading,Locking,Atomic,Interlocked,我有一个简单的类,如: public class XXX { double val1; double val2; double delta; public void SetValues(double v1, double v2) { val1 = v1; val2 = v2; delta = val1 - val2; } public double Val1 { get { return val
public class XXX
{
double val1;
double val2;
double delta;
public void SetValues(double v1, double v2)
{
val1 = v1;
val2 = v2;
delta = val1 - val2;
}
public double Val1 { get { return val1; } }
public double Val2 { get { return val2; } }
public double Delta { get { return delta; } }
}
,一个线程用于设置值,多个线程用于读取值。因此,可以使用lock()
使所有读写不间断。
但是我知道同步永远不会实现,总是有机会Val1-Val2
可能不等于Delta
,我不太在乎。我更关心的是通过getter获得稳定的值。但是,在这种情况下,lock()
的成本很高,因为大多数读卡器都可以工作
我想到的第二件好事是使用Interlocked.Exchange()
但代码对我来说似乎很愚蠢。我不知道
那么
lock()
有意义吗?我是否应该使用Interlocked.Exchange()
来提高性能?或者我还能做什么?对于我将使用的多个读者/作者场景
对于多个读写器场景,我将使用
您需要锁定整个SetValues方法:
private object lockObject = new object();
public void SetValues(double v1, double v2)
{
lock(lockObject)
{
val1 = v1;
val2 = v2;
delta = val1 - val2;
}
}
public double Val1 { get { lock(lockObject) { return val1; } } }
public double Val2 { get { lock(lockObject) { return val2; } } }
public double Delta { get { lock(lockObject) { return delta; } } }
读者仍然可以得到不属于一起的Val1、Val2和Delta,因为他们分几个步骤阅读
您可以将Val1、Val2和Delta放入一个值对象中,该值对象可以一次性检索,并且不会更改:
public Values Val1
{
get
{
lock(lockObject)
{
// create a consistent object which holds a copy of the values
return new Values(val1, val2, delta);
}
}
}
struct Values
{
// ...
public double Val1 { get /* ... */ }
public double Val2 { get /* ... */ }
public double Delta { get /* ... */ }
}
您需要锁定整个SetValues方法:
private object lockObject = new object();
public void SetValues(double v1, double v2)
{
lock(lockObject)
{
val1 = v1;
val2 = v2;
delta = val1 - val2;
}
}
public double Val1 { get { lock(lockObject) { return val1; } } }
public double Val2 { get { lock(lockObject) { return val2; } } }
public double Delta { get { lock(lockObject) { return delta; } } }
读者仍然可以得到不属于一起的Val1、Val2和Delta,因为他们分几个步骤阅读
您可以将Val1、Val2和Delta放入一个值对象中,该值对象可以一次性检索,并且不会更改:
public Values Val1
{
get
{
lock(lockObject)
{
// create a consistent object which holds a copy of the values
return new Values(val1, val2, delta);
}
}
}
struct Values
{
// ...
public double Val1 { get /* ... */ }
public double Val2 { get /* ... */ }
public double Delta { get /* ... */ }
}
如果您需要getter返回稳定的结果,并且只需要维护内部同步,那么我建议您创建一个名为“Snapshot”(包含Val1、Val2和Delta)或类似的类。在setter中,构建此类的新副本并将其交换为实例变量。在getter中,只需返回快照的当前副本。只要调用方需要一致的体验,他们就会使用从单个getter调用返回的单个快照实例 因此,您必须放弃使用多个getter,否则就无法(没有外部同步)保证Val1、Val2和Delta是一致的
如果您需要getter返回稳定的结果,并且只需要维护内部同步,那么我建议您创建一个名为“Snapshot”(包含Val1、Val2和Delta)或类似的类。在setter中,构建此类的新副本并将其交换为实例变量。在getter中,只需返回快照的当前副本。只要调用方需要一致的体验,他们就会使用从单个getter调用返回的单个快照实例 因此,您必须放弃使用多个getter,否则就无法(没有外部同步)保证Val1、Val2和Delta是一致的
嗯<代码>变量foo=new XXX();var v1=foo.Val1;var v2=foo.Val2;var d=foo.Delta;断言相等(增量,v1-v2)代码>可能仍然会失败。请告诉我,除非我倾向于使用互斥作为个人偏好。@Spaceghost:没有区别。您的线程仍然可以在读取foo.Delta和foo.V1.Huh之间被抢占<代码>变量foo=new XXX();var v1=foo.Val1;var v2=foo.Val2;var d=foo.Delta;断言相等(增量,v1-v2)代码>可能仍然会失败。请告诉我,除非我倾向于使用互斥作为个人偏好。@Spaceghost:没有区别。您的线程仍然可以在读取foo.Delta和foo.V1之间被抢占。标准的
锁通常比ReaderWriterLockSlim
便宜,这取决于读写比。@LukeH-这正是我想要的。谢谢。一个标准的锁
通常比读写锁
便宜,这取决于读写比。@LukeH-这正是我想要的。谢谢。+1,但是联锁的.Exchange
调用是不必要的,因为引用赋值保证是原子的。这样做可以完全无锁。@LukeH-我真的记不起来了。如果这是真的,interlocated.Exchange(Object,Object)
的目的是什么?我想您可能希望将旧值作为原子操作的一部分:例如,var oldVal=\u val_val=newVal
,这两个操作都是单独的原子操作,但不能保证oldVal
在被newVal
@LukeH覆盖之前的某一点包含\u val
的内容-是,我忘了返回值在那里很有用。@sad_man-你必须将xxx.Current
存储到读取代码的变量中,然后使用该变量保持一致性。因此,我引用了“他们将使用从单个getter调用返回的单个快照实例”+1,但是Interlocked.Exchange
调用是不必要的,因为引用分配保证是原子的。这样做可以完全无锁。@LukeH-我真的记不起来了。如果这是真的,interlocated.Exchange(Object,Object)
的目的是什么?我想您可能希望将旧值作为原子操作的一部分:例如,var oldVal=\u val_val=newVal
,这两个操作都是单独的原子操作,但不能保证oldVal
在被newVal
@LukeH覆盖之前的某一点包含\u val
的内容-是,我忘了返回值在那里很有用。@sad_man-你必须将xxx.Current
存储到读取代码的变量中,然后使用该变量保持一致性。因此我引用了“他们将使用从单个getter调用返回的单个快照实例”
public class XXX
{
public class Snapshot {
double val1;
double val2;
double delta;
public Snapshot (double val1,double val2)
{
this.val1 = val1;
this.val2 = val2;
this.delta = val1 - val2;
}
public double Val1 { get { return val1; } }
public double Val2 { get { return val2; } }
public double Delta { get { return delta; } }
}
Snapshot _current;
public void SetValues(double v1, double v2)
{
Snapshot s = new Snapshot(v1,v2);
/* If there were subsequent steps needed to get the snapshot "ready", you could do them here.
Otherwise, I think you can do this as a single assignment into _current above */
_current = s;
}
public Snapshot Current { get { return _current; } }
}