[英]Thread Safe Properties in C#
我正在尝试在 C# 中创建线程安全属性,并且我想确保我在正确的路径上 - 这是我所做的 -
private readonly object AvgBuyPriceLocker = new object();
private double _AvgBuyPrice;
private double AvgBuyPrice
{
get
{
lock (AvgBuyPriceLocker)
{
return _AvgBuyPrice;
}
}
set
{
lock (AvgBuyPriceLocker)
{
_AvgBuyPrice = value;
}
}
}
阅读这篇文章,似乎这不是正确的做法 -
然而,这篇文章似乎另有暗示,
http://www.codeproject.com/KB/cs/Synchronized.aspx
有人有更权威的答案吗?
编辑:
我想为这个属性做 Getter/Setter 的原因是 b/c 我实际上希望它在设置时触发一个事件 - 所以代码实际上是这样的 -
public class PLTracker
{
public PLEvents Events;
private readonly object AvgBuyPriceLocker = new object();
private double _AvgBuyPrice;
private double AvgBuyPrice
{
get
{
lock (AvgBuyPriceLocker)
{
return _AvgBuyPrice;
}
}
set
{
lock (AvgBuyPriceLocker)
{
Events.AvgBuyPriceUpdate(value);
_AvgBuyPrice = value;
}
}
}
}
public class PLEvents
{
public delegate void PLUpdateHandler(double Update);
public event PLUpdateHandler AvgBuyPriceUpdateListener;
public void AvgBuyPriceUpdate(double AvgBuyPrice)
{
lock (this)
{
try
{
if (AvgBuyPriceUpdateListener!= null)
{
AvgBuyPriceUpdateListener(AvgBuyPrice);
}
else
{
throw new Exception("AvgBuyPriceUpdateListener is null");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
我对使我的代码线程安全很陌生,所以请随时告诉我我是否以完全错误的方式进行处理!
将要
你写的锁是没有意义的。 例如,读取变量的线程将:
没有什么可以阻止另一个线程在第 3 步之后修改值。由于 .NET 中的变量访问是原子的(请参阅下面的警告),锁在这里实际上并没有多大作用:只是增加了开销。 与解锁示例对比:
另一个线程可能会更改步骤 1 和 2 之间的值,这与锁定示例没有什么不同。
如果要确保在进行某些处理时状态不会改变,则必须读取该值并在锁的上下文中使用该值进行处理:
话虽如此,有些情况下访问变量时需要锁定。 这些通常是由于底层处理器的原因:例如,不能在 32 位机器上作为单个指令读取或写入double
变量,因此您必须锁定(或使用替代策略)以确保损坏的值不被读。
由于您有一个原始值,此锁定可以正常工作 - 另一个问题中的问题是属性值是一个更复杂的类(可变引用类型) - 锁定将保护访问和检索持有的 double 值的实例你的班。
如果您的属性值是可变引用类型,另一方面,一旦使用其方法检索到类实例,锁定将无法防止更改类实例,这是其他发布者希望它做的。
线程安全不是您应该添加到变量中的东西,而是您应该添加到“逻辑”中的东西。 如果你给所有的变量加锁,你的代码仍然不一定是线程安全的,但它会很慢。 要编写线程安全程序,请查看您的代码并确定多个线程可以在哪里使用相同的数据/对象。 在所有这些关键位置添加锁或其他安全措施。
例如,假设以下伪代码位:
void updateAvgBuyPrice()
{
float oldPrice = AvgBuyPrice;
float newPrice = oldPrice + <Some other logic here>
//Some more new price calculation here
AvgBuyPrice = newPrice;
}
如果同时从多个线程调用此代码,则您的锁定逻辑没有用。 想象线程 A 获取 AvgBuyPrice 并进行一些计算。 现在在完成之前,线程 B 也正在获取 AvgBuyPrice 并开始计算。 与此同时,线程 A 完成并将新值分配给 AvgBuyPrice。 然而,仅仅片刻之后,它就会被线程 B(仍然使用旧值)覆盖,并且线程 A 的工作已经完全丢失。
那么你如何解决这个问题? 如果我们要使用锁(这将是最丑陋和最慢的解决方案,但如果您刚开始使用多线程则最简单),我们需要将所有更改 AvgBuyPrice 的逻辑放在锁中:
void updateAvgBuyPrice()
{
lock(AvgBuyPriceLocker)
{
float oldPrice = AvgBuyPrice;
float newPrice = oldPrice + <Some other code here>
//Some more new price calculation here
AvgBuyPrice = newPrice;
}
}
现在,如果线程 B 想要在线程 A 仍然忙碌时进行计算,它将等待线程 A 完成,然后使用新值完成其工作。 但请记住,任何其他修改 AvgBuyPrice 的代码也应该在 AvgBuyPriceLocker 工作时锁定它!
不过,如果经常使用,这会很慢。 锁很昂贵,还有很多其他机制可以避免锁,只需搜索无锁算法即可。
无论如何,双精度的读取和写入是原子的(
源)
双精度的读取和写入不是原子的,因此有必要使用锁保护对双精度的访问,但是对于许多类型,读取和写入是原子的,因此以下内容同样安全:
private float AvgBuyPrice
{
get;
set;
}
我的观点是线程安全比简单地保护每个属性更复杂。 举一个简单的例子,假设我有两个属性AvgBuyPrice
和StringAvgBuyPrice
:
private string StringAvgBuyPrice { get; set; }
private float AvgBuyPrice { get; set; }
假设我这样更新平均购买价格:
this.AvgBuyPrice = value;
this.StringAvgBuyPrice = value.ToString();
这显然不是线程安全的,并且以上述方式单独保护属性根本无济于事。 在这种情况下,应该在不同的级别而不是在每个属性级别执行锁定。
虽然是一个老问题,但它位于谷歌搜索之上,所以我添加了一个回复。 在您的示例中, get
储物柜在return
后不会被释放。 因此,我建议在这种情况下在try-finally
块中使用ReaderWriterLockSlim
,这非常适合您尝试完成的结果。 它允许多个线程进行读取或独占访问进行写入:
private readonly ReaderWriterLockSlim AvgBuyPriceLocker = new ReaderWriterLockSlim();
private double _AvgBuyPrice = 0;
public double AvgBuyPrice {
get {
AvgBuyPriceLocker.EnterReadLock();
try { return _AvgBuyPrice; }
finally { AvgBuyPriceLocker.ExitReadLock(); }
}
set {
AvgBuyPriceLocker.EnterWriteLock();
try { _AvgBuyPrice = value; }
finally { AvgBuyPriceLocker.ExitWriteLock(); }
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.