代码之家  ›  专栏  ›  技术社区  ›  NtFreX

.NET JIT编译器易失性优化

  •  13
  • NtFreX  · 技术社区  · 6 年前

    https://msdn.microsoft.com/en-us/magazine/jj883956.aspx

    考虑轮询循环模式:

    private bool _flag = true; 
    public void Run() 
    {
        // Set _flag to false on another thread
        new Thread(() => { _flag = false; }).Start();
        // Poll the _flag field until it is set to false
        while (_flag) ;
        // The loop might never terminate! 
    } 
    

    在这种情况下,.NET 4.5 JIT编译器可能会像这样重写循环:

    if (_flag) { while (true); } 
    

    如果在另一个线程上为false,优化可能会导致挂起。

    请参阅12月份的文章,以获取对这种模式的更详细解释。)

    如果我锁定,JIT编译器还会像上面所示优化代码吗 _flag 或者只会成功 volatile 停止优化?

    Eric Lippert对volatile有以下几点看法:

    田野是一个迹象,表明你正在做一些彻头彻尾的疯狂:你是 试图在两个不同的线程上读写相同的值 没有锁好。锁保证内存读或写 修改后的锁内观察是一致的,锁保证 一次只有一个线程访问给定的内存块,并且 很小,而且你可能会把代码弄错 因为你不明白确切的内存模型是非常大的。我 除了最琐碎的代码外,不要尝试编写任何低锁代码 联锁操作的使用。我把“volatile”的用法留给

    总结一下: 不稳定的 ? 还有 lock 声明?或者别的什么?

    因为埃里克·利珀特不鼓励你使用 不稳定的 一定还有别的事?


    下流选民:


    bool变量不是线程同步原语:


    复制: 这个问题是关于优化的。你链接的那个没有提到优化。

    4 回复  |  直到 6 年前
        1
  •  12
  •   Eric Lippert    6 年前

    我们来回答被问到的问题:

    如果我锁定了\u标志,JIT编译器还会像上面所示那样优化代码吗?还是只会让它变得不稳定,从而停止优化?

    好吧,我们不要回答被问到的问题,因为那个问题太复杂了。让我们把它分解成一系列不那么复杂的问题。

    如果我锁定了标志,JIT编译器还会像上面所示优化代码吗?

    lock 给一个 更强 保证比 volatile ,所以不,如果在读取的周围有一个锁,抖动将不允许将读取提升出循环 _flag 锁也必须在写的周围 . 锁只有在你使用的时候才起作用 到处 .

    private bool _flag = true; 
    private object _flagLock = new object();
    public void Run() 
    {
      new Thread(() => { lock(_flaglock) _flag = false; }).Start();
      while (true)
        lock (_flaglock)
          if (!_flag)
            break;
    } 
    

    极其糟糕 等待一个线程向另一个线程发出信号的方法。永远不要坐在一个严密的环投票国旗!像个明智的人一样使用等待手柄。)

    你说锁比挥发物强;那是什么意思?

    更多

    有关详细信息,请阅读C规范中有关特殊副作用的部分。

    一如既往,我要提醒你 挥发物不能保证你的新鲜度

    锁阻止了这种优化;挥发物阻止了这种优化。这些是唯一阻碍优化的因素吗?

    Interlocked 操作,或者可以显式引入内存限制。

    不稳定的

    不。

    我该怎么办?

    首先不要编写多线程程序。在一个程序中有多个控制线程是个坏主意。

    如果必须跨线程共享内存,请使用 ,而不是 最低水平 . 使用 CancellationToken 表示在异步工作流的其他位置取消的操作。

        2
  •  2
  •   Mgetz    6 年前

    lock Interlocked Operations 您告诉编译器块或内存位置存在数据争用,并且它不能对访问这些位置进行假设。因此,编译器放弃了在无数据竞争环境中执行的优化。这个隐含的契约还意味着您要告诉编译器您将以适当的无数据竞争的方式访问这些位置。

        3
  •  2
  •   usr    6 年前

    这个问题是关于优化的

    这不是正确的观点。重要的是如何指定语言的行为。JIT只能在不违反规范的约束下进行优化。因此,优化对程序是不可见的。这个问题中的代码的问题不是它正在被优化。问题是规范中的任何内容都不能强制程序正确。为了解决这个问题


    你不能上锁 _flag . lock Monitor _旗帜

    取消一个循环 CancellationTokenSource 因为这些天。它在内部使用volatile访问,但对您隐藏了它。循环轮询 CancellationToken CancellationTokenSource.Cancel() . 这是非常自我记录和易于实现。

    _旗帜

    object lockObj = new object(); //need any heap object to lock
    ...
    
    while (true) {
     lock (lockObj) {
      if (_flag) break;
     }
     ...
    }
    
    ...
    
    lock (lockObj) _flag = true;
    

    你也可以使用 volatile . ericlippert说得很对,如果你不需要的话,最好不要碰硬核线程。

        4
  •  0
  •   John Z. Li    6 年前

    volatile关键字实现了所谓的acquire和release语义,因此使用它来进行简单的线程同步是完全合法的。任何符合标准的JIT引擎都不应该优化它。

    当然,它是一种虚假的语言特性,因为C/C++具有不同的语义,这是大多数程序员可能已经习惯的。所以“volatile”的C#specific和Windows specific(ARM架构除外)用法有时令人困惑。