Scala.concurrent.blocking的坏用例是什么?

4

参考这个被接受的答案中提到的第三点,是否存在使用blocking对于正在'within' Future中执行的长时间计算(无论是CPU-bound还是IO-bound)毫无意义或不好的情况?

2个回答

5

从我的实践来看, blocking + ForkJoinPool 可能会导致连续且不可控的线程创建,如果您有很多需要处理的消息,并且每个消息都需要长时间阻塞(这也意味着在此期间它占用一些内存)。ForkJoinPool 会创建新线程以补偿“可管理的阻塞”线程,而不考虑 MaxThreadCount;在 VisualVm 中可能会出现数百个线程。它几乎杀死了反压力,因为池队列中始终有任务的位置(如果您的反压力基于 ThreadPoolExecutor 的策略)。性能会受到新线程分配和垃圾回收的影响。

所以:

  • 当消息速率不高于 1/blocking_time 时,使用线程的全部性能是很好的。一些智能反压可能有助于减缓传入消息的速度。
  • 如果任务在 blocking{}(无锁)期间实际上使用了您的 CPU,那么它只会增加线程数而不是系统中真实核心的数量,这是毫无意义的。
  • 对于任何其他情况都不好 - 您应该使用单独的固定线程池(和可能的轮询)。

P.S. blocking 被隐藏在 Await.result 中,因此并不总是明显的。在我们的项目中,有人在某些底层工作 actor 中执行了这样的 Await


5
这取决于执行FutureExecutionContext。如果ExecutionContext不是BlockContext,那么使用blocking就没有意义。也就是说,它将使用DefaultBlockContext,该上下文仅执行代码而没有任何特殊处理。虽然它可能不会增加太多开销,但还是没有意义的。Scala的ExecutionContext.Implicits.global是为了在线程池即将耗尽时在ForkJoinPool中生成新线程。也就是说,如果它通过blocking知道即将发生这种情况。如果你正在生成大量线程,这可能是不好的。如果你在短时间内排队了很多工作,global上下文将愉快地扩展直到死锁。@dk14的答案更详细地解释了这一点,但要点是,它可能会成为性能杀手,因为管理阻塞实际上可能很快变得难以管理。 blocking的主要目的是避免线程池中的死锁,因此它与性能有间接关系,因为达到死锁比生成更多线程更糟糕。但是,它绝对不是神奇的性能增强器。我在这个答案中更详细地介绍了blocking

顺便问一下,也许你知道除了ForkJoinPool之外还有哪些支持阻塞的实现吗?我没有找到... - dk14

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接