繁体   English   中英

C# 中的线程安全属性

[英]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;
        }
    }
}

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

带有 get/set 的 C# 线程安全

然而,这篇文章似乎另有暗示,

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

我对使我的代码线程安全很陌生,所以请随时告诉我我是否以完全错误的方式进行处理!

将要

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

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

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

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

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

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

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

话虽如此,有些情况下访问变量时需要锁定。 这些通常是由于底层处理器的原因:例如,不能在 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;
}

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

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM