C# C语言中的线程安全属性#

C# C语言中的线程安全属性#,c#,.net,multithreading,properties,thread-safety,C#,.net,Multithreading,Properties,Thread Safety,我正在尝试在C#中创建线程安全属性,我希望确保我在正确的路径上-以下是我所做的- private readonly object AvgBuyPriceLocker = new object(); private double _AvgBuyPrice; private double AvgBuyPrice { get { lock (AvgBuyPriceLocker) { return _AvgBuyPrice;

我正在尝试在C#中创建线程安全属性,我希望确保我在正确的路径上-以下是我所做的-

private readonly object AvgBuyPriceLocker = new object();
private double _AvgBuyPrice;
private double AvgBuyPrice 
{
    get
    {
        lock (AvgBuyPriceLocker)
        {
            return _AvgBuyPrice;
        }
    }
    set
    {
        lock (AvgBuyPriceLocker)
        {
            _AvgBuyPrice = value;
        }
    }
}
读了这篇文章,似乎这不是正确的做法-

但这篇文章似乎提出了相反的建议,

有人有更明确的答案吗

编辑:

我想为这个属性执行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);
            }
        }
    }
}
我对使我的代码线程安全还很陌生,所以如果我用了完全错误的方法,请随时告诉我


Will

由于您有一个原语值,因此此锁定将正常工作-另一个问题中的问题是属性值是一个更复杂的类(可变引用类型)-锁定将保护访问和检索您的类所持有的双精度值的实例

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

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

private float AvgBuyPrice
{
    get;
    set;
}
我的观点是线程安全比简单地保护每个属性更复杂。举一个简单的例子,假设我有两个属性
AvgBuyPrice
StringAvgBuyPrice

private string StringAvgBuyPrice { get; set; }
private float AvgBuyPrice { get; set; }
假设我更新了平均购买价格:

this.AvgBuyPrice = value;
this.StringAvgBuyPrice = value.ToString();

这显然不是线程安全的,以上述方式单独保护属性毫无帮助。在这种情况下,锁定应该在不同的级别上执行,而不是在每个属性级别上执行。

您编写的锁是没有意义的。例如,读取变量的线程将:

  • 拿到锁
  • 读取值
  • 松开锁
  • 以某种方式使用读取值
  • 在步骤3之后,没有什么可以阻止另一个线程修改该值。由于.NET中的变量访问是原子性的(请参见下面的警告),因此锁在这里实际上并没有起到多大作用:只是增加了开销。与解锁示例相比:

  • 读取值
  • 以某种方式使用读取值
  • 另一个线程可能会在步骤1和步骤2之间更改值,这与锁定的示例没有什么不同

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

  • 拿到锁
  • 读取值
  • 以某种方式使用读取值
  • 松开锁

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

    线程安全性不应添加到变量中,这是你应该添加到你的“逻辑”中的东西。如果向所有变量添加锁,代码仍然不一定是线程安全的,但速度会非常慢。 要编写线程安全程序,请查看您的代码并确定多个线程可以在何处使用相同的数据/对象。在所有关键位置加锁或其他安全措施

    例如,假设以下一位伪代码:

    void updateAvgBuyPrice()
    {
        float oldPrice = AvgBuyPrice;
        float newPrice = oldPrice + <Some other logic here>
        //Some more new price calculation here
        AvgBuyPrice = newPrice;
    }
    
    void updateAvgBuyPrice()
    {
    浮动oldPrice=AvgBuyPrice;
    浮动新价格=旧价格+
    //这里有一些新的价格计算
    AvgBuyPrice=新价格;
    }
    
    如果同时从多个线程调用此代码,则锁定逻辑没有用处。想象一个线程获取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;
        }
    }
    
    void updateAvgBuyPrice()
    {
    锁(AvgBuyPriceLocker)
    {
    浮动oldPrice=AvgBuyPrice;
    浮动新价格=旧价格+
    //这里有一些新的价格计算
    AvgBuyPrice=新价格;
    }
    }
    
    现在,如果线程B想在线程A仍然忙的时候进行计算,它将等待线程A完成,然后使用新值进行工作。但是请记住,任何其他也修改AvgBuyPrice的代码在工作时也应该锁定AvgBuyPriceLocker


    不过,如果经常使用,速度会很慢。锁很昂贵,还有很多其他机制可以避免锁,只需搜索无锁算法。

    虽然这是一个老问题,但它在谷歌搜索中占据了上风,所以我添加了一个回答。在您的示例中,
    get
    locker在
    return
    之后不会被释放。因此,在这种情况下,我建议在
    try finally
    块中使用
    readerwriterlocksim
    ,这非常适合您尝试实现的结果。它允许多线程读取或独占访问写入:

    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(); }
        }
    }
    

    没关系。我不明白,为什么你认为不是。链接的SO答案并不表示这样做是个坏主意。var property=new ConcurrentValue();property.ReadValue(x=>