为什么方法调用会将非易失性变量的值刷新到主线程?

本文关键字:刷新 线程 变量 调用 方法 易失性 为什么 | 更新日期: 2023-09-27 18:12:56

为什么getter Val恰好模拟字段val的波动?

我假设利用方法调用并不是保持变量不稳定的可靠方法。

(要尝试一下,构建版本并直接执行,而不需要调试器。)

class Program
{
    private int val = 0;
    public int Val { get { return val; } }
    public static void Main()
    {
        var example = new Program();
        Task.Run(() => example.val++);
        while (example.val == 0) ; // Hangs if val is not volatile
        while (example.Val == 0) ; // Never seems to hang
    }
}

为什么方法调用会将非易失性变量的值刷新到主线程?

好吧,事实证明,抖动被允许假设所有非易失性变量只被一个线程访问(很像在c++ 11内存模型中并发访问非std::atomic<>变量调用未定义行为)。在这种情况下,抖动正在将第一个循环优化到loop: test eax, eax; je loop(它将变量访问提升到一个永远不会更新的寄存器中),因此显然它永远不会终止。

第二个循环生成相对于对象指针读取值的汇编,因此最终它会看到新值(尽管相对于另一个线程上的其他写操作可能是无序的,同样是因为该变量不是易失性的)。这是巧合,因为碰巧生成了程序集。

第一个(无限)循环生成的x86汇编:

003B23BA  test        eax,eax  
003B23BC  je          003B23BA  

第二次(有限)循环的x86程序集:

002F2607  cmp         dword ptr [eax+4],0  
002F260B  je          002F2607

由于抖动允许假定非易失性变量从未被其他线程接触,您只能依赖volatile按预期工作(即使它在给定的情况下出现,比如这个,因为未来的优化(或不同的CPU架构等)可能会以难以调试的方式破坏您的代码)。