关于.net:C#中的线程安全属性

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位计算机上,double变量不能作为单个指令读取或写入,因此必须锁定(或使用其他策略)以确保不会读取损坏的值。


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

    另一方面,如果属性值是可变的引用类型,则一旦使用类实例的方法检索到类实例,锁定将无法保护类实例的更改,而这正是另一个发布程序希望它做的。


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

    例如,假设以下是伪代码:

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

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

    1
    2
    private string StringAvgBuyPrice { get; set; }
    private float AvgBuyPrice { get; set; }

    假设我将平均购买价格更新为:

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

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