Future 中的空 while 循环导致 Future 在 Scala 中永远不会 return

Empty while loop in Future causes Future to never return in Scala

我的问题可能很模糊(想不出如​​何描述它)但希望这个例子能让事情更清楚:

class IntTestFake extends FunSpec with ScalaFutures {

  describe("This"){
    it("Fails for some reason"){

      var a = "Chicken"

      val b = "Steak"

      def timeout() = Future{
        while(a != b){}
      }

      Future{
        Thread.sleep(3000)
        a = b
      }

      whenReady(timeout(), Timeout(20 seconds), Interval(50 milliseconds))(result => result)
    }

    it("Passes...why?!?"){
      var a = "Chicken"

      val b = "Steak"

      def timeout() = Future{
        while(a != b){
          println("this works...")
        }
      }

      Future{
        Thread.sleep(3000)
        a = b
      }

      whenReady(timeout(), Timeout(20 seconds), Interval(50 milliseconds))(result => result)
    }
  }
}

在第一个测试 (Fails for some reason) 中,while 循环有一个空循环体。在第二个测试 (Passes...why?!?) 中,while 循环体中有一个 println 语句。我最初的想法是垃圾收集正在做一些时髦的事情,但是对于那个 whenReady 声明,我期待 return 的东西,所以我希望 GC 在那之前不要管它。很抱歉,如果有人问过我找不到示例。

问题是代码正在从两个线程读取 var 而没有警告编译器它将要执行此操作,这会导致不可预测的行为。编译器不知道 a 的值将在其脚下发生变化,因此完全允许将该值缓存在寄存器或其他内存中。如果是这样,while 循环将永远旋转。

碰巧您的第一个测试失败而第二个测试成功,但这是您使用的特定编译器和调度程序的结果,并且在不同的系统上可能会有所不同。

解决方案是避免使用共享变量并使用适当的同步机制。在这种情况下,Promise 可能会成功。

a 需要是 @volatile,没有它,从其他线程写入不能保证对当前线程可见,直到它命中 "memory barrier"(一个特殊点在代码,其中所有缓存都被刷新 - 正如评论中指出的 概念意义上的 ,不一定直接映射到特定 cpu 硬件如何处理它)。这就是第二种情况起作用的原因 - println 调用中有很多内存障碍。

因此,将 var a ... 更改为 @volatile var a ... 将使其工作...但是,严重的是,不要使用 vars。至少,直到你学会了足够多的 scala 才能识别你必须拥有它们的情况。