【问题标题】:Do properties have volatile effect?属性是否具有波动性影响?
【发布时间】:2012-05-29 09:22:03
【问题描述】:

在下面的代码中read1 总是等于read2,前提是属性Flag 可以从其他线程更改?这里担心的是Flag 可能会被内联。

private bool Flag {get; set;}

public void MultithreadedMethod()
{
    var read1 = Flag;

    /* some more code */

    var read2 = Flag;    
}

UPD:在/* some more code */ 执行期间,其他一些线程可能会更改Flag 的值。在这种情况下,read1 应该不同于 read2总是会这样吗?尽管Flag 在读取之间发生了更改,但内联不会将属性转换为非易失性字段,这将导致read1 等于read2

【问题讨论】:

  • 我认为没有理由保证。当然,volatile 通常也没有人们想象的那么大的意义。如果重要的话,我个人会看lockInterlocked

标签: c# multithreading concurrency


【解决方案1】:

不,该属性不是volatile

虽然我无法为您的初始场景获得令人满意的演示,但这种替代方法应该可以很好地证明该陈述:

class Program
{
    public bool Flag { get; set; }

    public void VolatilityTest()
    {
        bool work = false;
        while (!Flag)
        {
            work = !work; // fake work simulation
        }
    }

    static void Main(string[] args)
    {
        Program p = new Program();
        var t = new Thread(p.VolatilityTest);
        t.Start();
        Thread.Sleep(1000);
        p.Flag = true;
        t.Join();
    }
}

在 Release 模式下构建它会使程序死锁,因此证明 Flag 没有易失行为(即它在读取之间得到“优化”)。

public bool Flag { get; set; } 替换为public volatile bool Flag; 将使程序正确终止。

【讨论】:

  • 另一种可能性是使支持字段可变。所以你仍然可以使用像 internal 一样的 set 修饰符。
  • 封装的 volatile 字段怎么样?私有 volatile bool 标志;和一个属性标志 { get { return flag; } , 设置 { 标志 = 值; } } ?
  • 非常有趣的一点,但在我的测试中,您的确切代码的发布版本没有锁定。在新控制台项目中的 Visual Studio 2013 中运行,因此优化设置是默认设置。我知道它是否被优化取决于实现,但我很难相信变量的实际值不会在 (!Flag) 时被检入,而是使用它的初始值。特别是当编译器可以确定该值确实发生了变化时。
  • @ILIA BROUDNO:这个例子取决于环境,尽管只要你有一台多核机器,它在 .Net 版本之间应该是相当一致的。我刚刚在四核 Xeon 上的 VS 2013、.Net 4.5 中对此进行了测试,我仍然可以重现它。确保在构建项目时在项目属性中启用“优化代码”。
  • @Tudor:感谢您的检查。最后 - Visual Studio 2013 .Net 4.5 四核 i5。检查优化代码。没有锁定。
【解决方案2】:

是的,它可以自然地改变。

即使在提供的代码中,也不能保证 read1 等于 read2

考虑到/* some more code */同时执行,Flag可能会受到其他线程的影响。

编辑

read1read2 的相等与是否内联无关,Flag 是一个bool,所以它是一个值类型。所以

  • var read1 = Flag; //假设 read1 为真
  • Flag = False
  • var read2 = Flag; //read2 为 FALSE,但 read1 仍为 TRUE

这在多线程环境中也有效,因为你操作的是值类型。

如果这不是您的要求,请澄清。

【讨论】:

  • 我认为考虑到 OP 的编辑,他要求假设线程更改 Flag/* some more code */ 正在执行,是否 保证 read2 会接关于这个变化。
【解决方案3】:

如果可以从其他线程更改 Flag,则不能保证 read1 和 read2 会相同。您必须在代码周围使用监视器/互斥锁,并确保标志设置器也尊重该互斥锁。

【讨论】:

  • 是的,但是否保证 read1 和 read2 会不同。
  • 保证它们会有所不同?当然不是。它们可能会有所不同,具体取决于正在运行的代码以及您在 MultithreadedMethod 中的代码。如果有另一个线程在操作 Flag,它们可能会有所不同。
【解决方案4】:

总结其他回复,在这种情况下,无法预测代码执行后两个变量的值会发生什么。既是因为 CLR 和编译器对我们来说在很大程度上是黑匣子,而且因为任何关于竞争条件结果的预测实际上都是一场赌博,在某些时候肯定会出错。

无论如何,您不能在多线程环境中编写此类代码。

【讨论】:

    【解决方案5】:

    autoproperty 缺乏 volatile 令人失望。我发现当使用带有 [StructLayout(LayoutKind.Sequential, Pack = 4)] 和 Marshal.PtrToStructure 的结构时,如果使用自动属性,字节布局不会按预期保留。我所做的是使用私有支持字段并将属性放在最后。

    【讨论】:

      【解决方案6】:

      根据documentation

      volatile关键字表示一个字段可以在 由诸如操作系统、硬件或 并发执行线程。 ... 使用 volatile 修饰符可确保一个线程检索最多 另一个线程写入的最新值。

      【讨论】:

      • 那么自动属性呢?它不能做到易变。但它会表现得不稳定吗?
      • 自动属性声明一个与属性相同类型的私有字段,并通过getter和setter访问它。私有属性没有任何特殊的修饰符,所以它不会像一个 volatile 字段。
      • 太糟糕了,您不能在自动属性上使用 volatile 导致基础字段为 volatile。抱歉,您的回答不能回答问题,只有您的 cmets 可以回答;-)。
      猜你喜欢
      • 2012-05-21
      • 1970-01-01
      • 1970-01-01
      • 2013-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-22
      • 1970-01-01
      相关资源
      最近更新 更多