C#中的线程安全属性

Wil*_*iam 34 .net c# multithreading properties thread-safety

我正在尝试在C#中创建线程安全属性,我想确保我在正确的路径上 - 这就是我所做的 -

private readonly object AvgBuyPriceLocker = new object();
private double _AvgBuyPrice;
private double AvgBuyPrice 
{
    get
    {
        lock (AvgBuyPriceLocker)
        {
            return _AvgBuyPrice;
        }
    }
    set
    {
        lock (AvgBuyPriceLocker)
        {
            _AvgBuyPrice = value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

阅读这篇文章,似乎这不是正确的做法 -

C#线程安全与get/set

但是,这篇文章似乎暗示,

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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我很擅长使我的代码线程安全,所以请随时告诉我,我是否以完全错误的方式处理它!

Pau*_*ane 26

你写的锁是没有意义的.例如,读取变量的线程将:

  1. 获得锁.
  2. 读取值.
  3. 解锁.
  4. 以某种方式使用读取值.

没有什么可以阻止另一个线程在步骤3之后修改该值.由于.NET中的变量访问是原子的(请参见下面的警告),这里的锁实际上并没有实现太多:仅增加开销.与解锁示例对比:

  1. 读取值.
  2. 以某种方式使用读取值.

另一个线程可能会改变步骤1和2之间的值,这与锁定的示例没有什么不同.

如果要确保在进行某些处理时状态不会更改,则必须读取该值在锁的上下文中使用该值进行处理:

  1. 获得锁.
  2. 读取值.
  3. 以某种方式使用读取值.
  4. 解锁.

话虽如此,有些情况下您需要在访问变量时锁定.这些通常是由于底层处理器的原因:例如,double变量无法在32位机器上作为单个指令读取或写入,因此您必须锁定(或使用替代策略)以确保不读取损坏的值.


Bro*_*ass 19

由于你有一个原始值,这个锁定工作正常 - 另一个问题的问题是属性值是一个更复杂的类(一个可变的引用类型) - 锁定将保护访问和检索由持有的double值的实例你的班.

另一方面,如果您的属性值是可变引用类型,则锁定将无法防止在使用其方法检索后更改类实例,这是另一个海报希望它执行的操作.


Mar*_*art 16

线程安全不是你应该添加到变量中的东西,它应该添加到你的"逻辑"中.如果你为所有变量添加锁,你的代码仍然不一定是线程安全的,但它会很慢.要编写一个线程安全的程序,请查看代码并确定多个线程可以使用相同数据/对象的位置.为所有关键位置添加锁或其他安全措施.

例如,假设以下伪代码:

void updateAvgBuyPrice()
{
    float oldPrice = AvgBuyPrice;
    float newPrice = oldPrice + <Some other logic here>
    //Some more new price calculation here
    AvgBuyPrice = newPrice;
}
Run Code Online (Sandbox Code Playgroud)

如果同时从多个线程调用此代码,则锁定逻辑无用.想象一下线程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;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,如果线程B想要在线程A仍然忙碌时进行计算,它将等待线程A完成,然后使用新值完成其工作.但请记住,任何其他也修改AvgBuyPrice的代码也应该锁定AvgBuyPriceLocker!

如果经常使用,这将是缓慢的.锁是昂贵的,还有很多其他机制来避免锁,只是搜索无锁算法.

  • 为了增加Mart的评论,Joseph Albahari撰写了一篇关于所有线程方面的优秀在线书籍(http://www.albahari.com/threading/).该书以HTML或PDF格式提供. (4认同)

Jus*_*tin 6

无论如何,读取和写入双精度都是原子的()读取和写入双精度不是原子的,因此有必要使用锁来保护对双精度的访问,但是对于许多类型来说,读取和写入是原子的,因此以下内容同样安全:

private float AvgBuyPrice
{
    get;
    set;
}
Run Code Online (Sandbox Code Playgroud)

我的观点是,线程安全比简单地保护每个属性更复杂.举个简单的例子,假设我有两个属性AvgBuyPriceStringAvgBuyPrice:

private string StringAvgBuyPrice { get; set; }
private float AvgBuyPrice { get; set; }
Run Code Online (Sandbox Code Playgroud)

并且假设我如此更新平均购买价格:

this.AvgBuyPrice = value;
this.StringAvgBuyPrice = value.ToString();
Run Code Online (Sandbox Code Playgroud)

这显然不是线程安全的,并且以上述方式单独保护属性根本无济于事.在这种情况下,锁定应该在不同的级别而不是在每个属性级别执行.

  • 根据你的MSDN源代码,双倍*不保证是原子的:"其他类型的读写,包括long,ulong,double和decimal,以及用户定义的类型,不能保证是原子的." (4认同)