[英]When to use scala.concurrent.blocking?
我在问自己这个问题:“你什么时候应该使用scala.concurrent.blocking
?”
如果我理解正确的话, blocking {}
只有与 ForkJoinPool 一起使用才有意义。 此外docs.scala-lang.org强调,阻塞不应该用于长时间运行的执行:
最后但并非最不重要的一点是,您必须记住 ForkJoinPool 不是为持久阻塞操作而设计的。
我假设长时间运行的执行是数据库调用或某种外部 IO。在这种情况下,应使用单独的线程池,例如 CachedThreadPool。 大多数 IO 相关框架,如 sttp、doobie、cats 都可以使用提供的 IO 线程池。
所以我问自己,阻塞语句仍然存在哪个用例? 这仅在处理锁定和等待操作(如信号量)时有用吗?
考虑线程池饥饿的问题。 假设您有一个包含 10 个可用线程的固定大小的线程池,如下所示:
implicit val myFixedThreadPool =
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(10))
如果由于某种原因所有 10 个线程都被占用,并且有一个新请求需要第 11 个线程来完成它的工作,那么第 11 个请求将挂起,直到其中一个线程可用。
blocking { Future {... } }
构造可以解释为请不要使用来自myFixedThreadPool
的线程,而是在myFixedThreadPool
之外启动一个新线程。
一个实际用例是,如果您的应用程序在概念上可以被视为分为两部分,一部分表示在 90% 的情况下与适当的异步 API 对话,但在少数特殊情况下必须与另一部分对话说一个非常慢的外部 API 需要很多秒才能响应并且我们无法控制。 将固定线程池用于真正的异步部分相对安全,不会出现线程池饥饿,但是对于第二部分也使用相同的固定线程池会出现这样的情况,即突然向缓慢的外部 API 发出 10 个请求,现在导致 90% 的其他请求挂起,等待那些缓慢的请求完成。 将那些缓慢的请求包装在blocking
中将有助于将 90% 的其他请求挂起的可能性降至最低。
从阻塞请求中实现真正异步请求的这种“泳道”的另一种方法是将阻塞请求卸载到一个单独的专用线程池,仅用于阻塞调用,就像这样
implicit val myDefaultPool =
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(10))
val myPoolForBlockingRequests =
ExecutionContext.fromExecutor(Executors.newFixedThreadPool(20))
Future {
callAsyncApi
} // consume thread from myDefaultPool
...
Future {
callBlockingApi
}(myPoolForBlockingRequests) // consume thread from myPoolForBlockingRequests
我在问自己这个问题:“你什么时候应该使用 scala.concurrent.blocking?”
好吧,因为这对Future
最有用,而Future
永远不应该用于严肃的业务逻辑,所以永远不要。
现在,撇开“笑话”不谈,当使用Futures
时,你应该在包装阻塞操作时始终使用blocking
,并接收自定义的ExecutionContext
; 而不是对global
进行硬编码。 请注意,情况应该总是如此,即使对于非阻塞操作也是如此,但 IME 大多数使用Future
的人不会这样做......但这是另一个讨论。
然后,那些阻塞操作的调用者可以决定他们是使用他们的计算 EC 还是阻塞的。
当文档提到持久时,它们并没有什么具体的意思,主要是因为很难具体说明; 是特定于上下文/应用程序的。 您需要了解的是,默认情况下blocking
(注意实际的 EC 可能会做任何他们想做的事)只会创建一个新线程,如果您创建了很多线程并且它们需要很长时间才能被释放,您将使您的 memory 饱和并且终止带有 OOM 错误的程序。
对于这些情况,建议控制应用程序的背压以避免创建过多线程。 一种方法是为您将支持的最大阻塞操作数创建一个固定的线程池,并将所有其他待处理任务排入队列; 这样的 EC 应该忽略blocking
调用。 您也可能只有无限数量的线程,但在代码的其他部分手动管理背压; 例如,对于明确的Queue
,这是以前的常见建议: https://gist.github.com/djspiewak/46b543800958cf61af6efa8e072bfd5c
但是,即使计算 EC 未被阻塞,阻塞线程总是会损害应用程序的性能。 Daniel 的最新演讲详细解释了这些内容:“效果系统案例”和“大规模线程”。
因此,生态系统正在努力推动艺术的 state 不惜一切代价避免这种情况,但这不是一项简单的任务。 尽管如此,运行时(如cats-effect或ZIO提供的运行时)已经过优化,可以尽可能地处理阻塞任务,并且在今年和明年可能会有所改进。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.