22

读取和写入 C# 中的某些基本类型,例如boolint是原子的。

(请参阅 C# 语言规范中的第 5.5 节,“5.5 变量引用的原子性”。)

但是通过属性访问这些变量呢?假设它们也将是原子的和线程安全的是否合理?例如,阅读MyProperty以下原子和线程安全吗?:

public bool MyProperty { get { return _foo; } }

那么自动实现的属性呢?

public bool MyProperty { get; }
4

6 回答 6

31

您需要更仔细地区分“原子”和“线程安全”。正如您所说,对于大多数内置值类型和引用,写入都是原子的。

但是,这并不意味着它们是线程安全的。这只是意味着如果值“A”和“B”都被写入,线程将永远不会看到介于两者之间的东西。(例如,从 1 到 4 的更改将永远不会显示 5 或 2,或除 1 或 4 之外的任何值。)这并不意味着一旦将值“B”写入变量,一个线程就会看到值“B”。为此,您需要根据波动性来查看内存模型。如果没有内存屏障,通常通过锁定和/或易失性变量获得,对主内存的写入可能会延迟并且读取可能会提前,有效地假设自上次读取以来该值没有改变。

如果您有一个计数器并且您询问它的最新值但由于缺少内存屏障而从未收到最新值,那么我认为您不能合理地调用该线程安全,即使每个操作很可能是原子的。

然而,这与属性无关 - 属性只是带有语法糖的方法。他们没有围绕线程做额外的保证。.NET 2.0 内存模型确实比 ECMA 模型有更多的保证,并且它有可能围绕方法进入和退出做出保证。这些保证也应该适用于属性,尽管我会对这些规则的解释感到紧张:有时很难对内存模型进行推理。

于 2009-07-20T06:00:48.600 回答
2

如果您检查自动生成的代码,您会发现自动生成的属性不是线程安全的——它们只是简单地获取/设置到生成的字段。事实上,这样做会对性能造成太大影响(尤其是在不需要时)。

此外,如果您计划从多个线程访问 int/bool 值,则应将其(字段)标记为volatile。这基本上可以防止与 CPU 寄存器相关的多线程问题。(这个是加锁,不是替代品)

于 2009-07-20T05:53:13.873 回答
2

我有点不清楚你在这里问什么。似乎您可能会问 2 个问题中的 1 个

  1. _foo调用 MyProperty 时的读取是原子的吗?
  2. MyProperty 的返回值是原子设置的吗?

对于#1,答案是肯定的。正如 C# 语言规范(和 CLI)所述,某些指定类型的变量的读取和写入保证是原子的。'bool' 类型就是其中一种类型。

至于#2,最好的地方是 CLI 规范的第 12.6.6 节。它指出

符合标准的 CLI 应保证对正确对齐的内存位置的读写访问不大于本机字大小(本机 int 类型的大小)是原子的

考虑到使用 MyProperty 的返回值,您必须读取或写入该值,可以安全地假设它是原子设置的。

于 2009-07-20T05:58:42.457 回答
1

原子性只是意味着如果多个线程正在写入一个变量,它的值不会被破坏(例如,如果它占用两个字节,你将永远不会得到线程一的高字节和线程二的低字节)。这并不意味着该变量是线程安全的。您仍然可以获得一个线程没有看到另一个线程更改等的常见线程问题。

为了线程安全,您需要使用锁定(或易失性,但这更难推理)。属性没有什么特别之处——它们也需要显式地设置为线程安全的。

于 2009-07-20T06:01:33.497 回答
0

我认为不会。属性本质上只是带有一些语法糖的方法,使它们更容易使用。因此,默认情况下,它们并不比普通方法调用更线程安全(也就是说,根本不是线程安全的)。

于 2009-07-20T05:53:59.440 回答
0

您可以在属性中放置任何内容,例如

    Public Property foo() As Boolean
        Get
           goToLunch()
           barbecueRibs()
           return m_foo
        End Get
        Set(ByVal value As Boolean)
           takeANap()
           accessDatabase()
           messUpOtherVariables()
           m_foo = value
        End Set
    End Property
于 2009-07-20T06:11:59.717 回答