我正在尝试在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);
}
}
}
}
我很擅长使我的代码线程安全,所以请随时告诉我是否以完全错误的方式处理它!
威尔
答案 0 :(得分:25)
你写的锁是没有意义的。例如,读取变量的线程将:
在步骤3之后,没有什么可以阻止另一个线程修改该值。由于.NET中的变量访问是原子的(请参见下面的警告),这里的锁实际上并没有实现太多:仅增加开销。与未解锁的示例形成对比:
另一个线程可能会改变步骤1和2之间的值,这与锁定的示例没有区别。
如果要确保在进行某些处理时状态不会发生变化,则必须读取值并在锁定的上下文中使用该值进行处理:
话虽如此,有些情况下您需要在访问变量时锁定。这些通常是由于底层处理器的原因:例如,double
变量无法在32位机器上作为单个指令读取或写入,因此您必须锁定(或使用替代策略)以确保不读取腐败值。
答案 1 :(得分:19)
由于你有一个原始值,这个锁定可以正常工作 - 另一个问题的问题是属性值是一个更复杂的类(一个可变的引用类型) - 锁定将保护访问和检索双重的实例你班上的价值。
如果你的属性值是一个可变引用类型,另一方面,一旦使用其方法检索,锁定将无法防止更改类实例,这是另一个海报希望它做的。
答案 2 :(得分:15)
线程安全不是你应该添加到变量中的东西,它应该添加到你的“逻辑”中。如果你为所有变量添加锁,你的代码仍然不一定是线程安全的,但它会很慢。 要编写一个线程安全的程序,请查看代码并确定多个线程可以使用相同数据/对象的位置。在所有关键位置添加锁或其他安全措施。
例如,假设下面的伪代码:
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工作时锁定它!
如果经常使用,这将会很慢。锁是昂贵的,还有很多其他机制来避免锁,只需搜索无锁算法。
答案 3 :(得分:6)
无论如何,读取和写入双精度都是原子的(source)读取和写入双精度不是原子的,因此有必要使用锁来保护对双精度的访问,但是对于许多类型来说,读写都是原子的,所以下面的内容同样安全:
private float AvgBuyPrice
{
get;
set;
}
我的观点是,线程安全比简单地保护每个属性更复杂。举一个简单的例子假设我有两个属性AvgBuyPrice
和StringAvgBuyPrice
:
private string StringAvgBuyPrice { get; set; }
private float AvgBuyPrice { get; set; }
假设我这样更新平均购买价格:
this.AvgBuyPrice = value;
this.StringAvgBuyPrice = value.ToString();
这显然不是线程安全的,并且以上述方式单独保护属性根本无济于事。在这种情况下,锁定应该在不同的级别而不是在每个属性级别执行。
答案 4 :(得分:-1)
虽然是个老问题,但它占据了 Google 搜索的首位,所以我添加了一个回复。在您的示例中,get
后不会释放 return
储物柜。因此,我建议在这种情况下在 ReaderWriterLockSlim
块中使用 try-finally
,这非常适合您尝试完成的结果。它允许多个线程进行读取或独占访问进行写入:
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(); }
}
}