【问题标题】:bad use cases of scala.concurrent.blocking?scala.concurrent.blocking 的坏用例?
【发布时间】:2015-04-16 00:42:00
【问题描述】:

参考this 接受的答案中的第三点,是否有任何情况下使用blocking 进行长时间运行的计算(无论是 CPU 还是 IO 绑定)是没有意义或不好的,即在Future'内'执行?

【问题讨论】:

    标签: scala concurrency blocking


    【解决方案1】:

    这取决于您的Future 正在执行的ExecutionContext。

    毫无意义:

    如果ExecutionContext 不是BlockContext,那么使用blocking 将毫无意义。也就是说,它将使用DefaultBlockContext,它只执行代码而无需任何特殊处理。它可能不会增加那么多开销,但仍然没有意义。

    不好:

    当线程池即将耗尽时,Scala 的ExecutionContext.Implicits.global 会在ForkJoinPool 中生成新线程。也就是说,如果它知道这将通过blocking 发生。如果您要生成 很多 个线程,这可能会很糟糕。如果您在短时间内排队处理大量工作,global 上下文将很高兴地扩展直到陷入僵局。 @dk14 的回答更深入地解释了这一点,但要点是它可能会成为性能杀手,因为托管阻塞实际上很快就会变得难以管理。


    blocking 的主要目的是避免线程池中的死锁,因此它与性能密切相关,因为达到死锁会比产生更多线程更糟糕。但是,它绝对不是一个神奇的性能增强器。

    我写过更多关于blocking 的文章,尤其是this answer。

    【讨论】:

    • 顺便说一句,也许你知道除了ForkJoinPool之外的任何其他阻塞感知实现?我没找到……
    【解决方案2】:

    From my practice, blocking + ForkJoinPool 如果您有大量消息要处理并且每条消息都需要长时间阻塞(这也意味着它在此期间持有一些内存),可能会导致线程的连续和无法控制的创建. ForkJoinPool 创建新线程来补偿“可管理阻塞”线程,不管MaxThreadCount;向 VisualVm 中的数百个线程问好。而且它几乎可以消除背压,因为池队列中总是有一个任务位置(如果您的背压基于ThreadPoolExecutor 的策略)。新线程分配和垃圾回收都会扼杀性能。

    所以:

    • 当消息率不高于 1/blocking_time 时很好,因为它允许您使用线程的全部功能。一些智能背压可能有助于减慢传入消息的速度。
    • 如果一个任务在blocking{}(无锁)期间实际使用您的 CPU 是没有意义的,因为它只会增加线程数而不是系统中的实际内核数。
    • 对于任何其他情况都不好 - 您应该使用单独的固定线程池(可能还有轮询)。

    附: blocking 隐藏在 Await.result 中,因此并不总是很明显。在我们的项目中,有人只是在一些底层的工人演员中做了这样的Await。

    【讨论】:

      猜你喜欢
      • 2013-11-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-11
      • 1970-01-01
      • 1970-01-01
      • 2012-04-07
      • 1970-01-01
      相关资源
      最近更新 更多