Thread Safe Properties in C#
我正试图在C中创建线程安全属性,我想确保我在正确的路径上-以下是我所做的-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | private readonly object AvgBuyPriceLocker = new object(); private double _AvgBuyPrice; private double AvgBuyPrice { get { lock (AvgBuyPriceLocker) { return _AvgBuyPrice; } } set { lock (AvgBuyPriceLocker) { _AvgBuyPrice = value; } } } |
读了这篇文章,似乎这不是正确的方法-
C带GET/SET的螺纹安全
然而,本文似乎另有建议,
http://www.codeproject.com/kb/cs/synchronized.aspx
有人有更明确的答案吗?
编辑:
我想为这个属性做getter/setter的原因是b/c,我实际上希望它在设置好之后触发一个事件,所以代码实际上是这样的-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | 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位计算机上,
由于您有一个原语值,这个锁定将工作正常-另一个问题是属性值是一个更复杂的类(可变的引用类型)-锁定将保护访问和检索您的类所持有的双值的实例。
另一方面,如果属性值是可变的引用类型,则一旦使用类实例的方法检索到类实例,锁定将无法保护类实例的更改,而这正是另一个发布程序希望它做的。
线程安全不是应该添加到变量中的东西,而是应该添加到"逻辑"中的东西。如果您向所有变量添加锁,您的代码仍然不一定是线程安全的,但速度会非常慢。要编写线程安全程序,请查看代码并确定多个线程可以在哪里使用相同的数据/对象。在所有这些关键位置加锁或其他安全措施。
例如,假设以下是伪代码:
1 2 3 4 5 6 7 | 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的逻辑放入锁中:
1 2 3 4 5 6 7 8 9 10 | 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!
不过,如果经常使用,速度会很慢。锁是昂贵的,有很多其他机制可以避免锁,只需搜索无锁算法。
reading and writing of double is atomic anythy(source)reading and writing of double is not atomic and so it would be necessary to protect access to a double using a lock,然而对于许多类型reading and writing is atomic and so the following would be just as safe:
1 2 3 4 5 | private float AvgBuyPrice { get; set; } |
我的观点是线程安全比简单地保护每个属性要复杂得多。举个简单的例子,假设我有两个属性
1 2 | private string StringAvgBuyPrice { get; set; } private float AvgBuyPrice { get; set; } |
假设我将平均购买价格更新为:
1 2 | this.AvgBuyPrice = value; this.StringAvgBuyPrice = value.ToString(); |
这显然不是线程安全的,用上面的方法单独保护属性毫无帮助。在这种情况下,锁定应该在不同的级别执行,而不是在每个属性级别执行。