Scala 的阻塞上下文似乎不能很好地处理混合阻塞/非阻塞作业。为什么?

Scala's blocking context doesn't seem to do well with mixed blocking / non-blocking jobs. Why?

我正在尝试理解 blocking 结构。虽然还不完全清楚它在内部是如何工作的,但我得到的一般想法是,只要我使用 Scala 的全局线程池,用 blocking 上下文包装我的代码将确保线程池会创建额外的 space 用于此作业(因为它不受 CPU 约束)。

  (1 to 1000).foreach { i =>
    Future {
      println(i)
      Thread.sleep(100 * 1000)
    }
  }

会很快显示只有8个作业可以同时运行,而

  (1 to 1000).foreach { i =>
    Future {
        blocking {
            println(i)
            Thread.sleep(100 * 1000)
        }
    }
  }

将显示现在我们有大约 250 个并发作业。哇! 然后让我措手不及的是

  (1 to 1000).foreach { i =>
    Future {
      println(i)
      Thread.sleep(100 * 1000)
    }
  }

  ('a' to 'z').foreach { c =>
    Future {
        blocking {
            println(c)
            Thread.sleep(100 * 1000)
        }
    }
  }

将再次仅显示 8 个并发作业 -- 阻塞作业不会立即执行。

这是为什么? blocking 上下文的内部机制究竟是什么?

您的第一个循环启动了 8 个线程,并阻塞了,因为它需要再启动 992 个 futures 才能完成。

不确定 "caught you off guard" 具体是什么。一旦第一个 foreach 调用完成,它将继续进行第二个调用,然后再开始 26 个。

blocking 只有在您进入阻塞上下文后才会生效。由于您有 8 个非阻塞期货 运行,它不会启动任何新的期货,因此它们无法进入阻塞上下文。换句话说,Scala 不会 "know" 它们会阻塞,直到它们开始执行。

您可以认为第二个代码段是这样工作的:

  1. 第一个未来已创建并启动。
  2. 第一个 future 通过调用 blocking 发出阻塞信号,因此该实现为更多 future 腾出了空间。
  3. 同时,在主线程上,创建并启动了第二个未来。
  4. ...

而你的最后一个片段是这样的:

  1. 第一个未来已创建并开始。它并不表示它正在阻塞。
  2. 第二个未来也是如此。
  3. ...
  4. 第 9 个 future 已创建,但尚未启动,因为有 8 个非阻塞 future。
  5. ...
  6. 第 1001 个 future(第二个循环中的第一个)已创建,但尚未启动,因为有 8 个非阻塞 future。由于它没有启动,它永远没有机会通过调用 blocking.
  7. 告诉实现它正在阻塞