multithreading - Scala Futures 如何在线程上运行?以及如何使用它们来执行异步和非阻塞代码?
问题描述
据我了解,在 Scala 中执行 IO 有 3 种方式,我将尝试用伪代码来表达。
一、同步&阻塞:
val syncAndBlocking: HttpResponse = someHttpClient.get("foo.com")
这里主线程只是空闲,直到响应返回..
其次,异步但仍然阻塞:
val asyncButBlocking: Future[HttpResponse] = Future { someHttpClient.get("bar.com") }
据我了解,这里的主线程是空闲的(因为 Future 在单独的线程上执行)但是该单独的线程被阻塞了..
第三,异步和非阻塞。我不确定如何实现那个,但据我所知,实现(例如 http 客户端)本身必须是非阻塞的,所以类似于:
val asynAndNotBlocking: Future[HttpResponse] = Future { someNonBlockingHttpClient.get("baz.com") }
我的问题是:
- 我上述假设是否有效?
- Scala 期货是在 OS 线程还是绿色线程上运行?或者这取决于执行上下文?
- 在 IO 是异步和非阻塞的第三种情况下,它是如何工作的?线程是否只启动任务(例如发送获取请求),然后再次空闲,直到响应到达时通过某种事件循环通知它?
解决方案
val syncAndBlocking: HttpResponse = someHttpClient.get("foo.com")
这将阻塞调用线程,直到收到响应(如您所述)。
val asyncButBlocking: Future[HttpResponse] = Future { someHttpClient.get("bar.com") }
与对 的任何调用一样Future.apply
,这(至少在概念上,可能存在消除某些步骤的优化):
- 创建一个
Function0[HttpResponse]
方法apply
为someHttpClient.get("bar.com")
. 如果someHttpClient
是静态的,理论上这可能发生在编译时(我不知道 Scala 编译器是否会执行此优化),否则它将发生在运行时。 - 将 thunk 传递给
Future.apply
,然后: - 创建一个
Promise[HttpResponse]
. ExecutionContext
在隐式传递的 to 上安排任务Future.apply
。此任务是调用 thunk:如果 thunk 成功执行,则与 thunk的结果Promise
相关联Future
(成功)完成,否则Future
失败(也是完成)与抛出的Throwable
(它可能仅在Throwable
匹配时失败) byNonFatal
,我懒得检查,在这种情况下致命的抛出被ExecutionContext
) 捕获。- 一旦在 thunk 上安排了任务
ExecutionContext
(可能在也可能不在 thunk 执行之前),就会返回Future
与 相关的。Promise
thunk 如何执行的细节取决于ExecutionContext
Scala 运行时的细节和扩展(对于 JVM 上的 Scala,thunk 将在由 确定的线程上运行ExecutionContext
,无论这是 OS 线程还是绿色线程依赖于 JVM,但操作系统线程至少目前可能是一个安全的假设(Project Loom 可能会影响这一点);对于 ScalaJS(因为 JavaScript 不公开线程)和 Scala Native(据我目前所知:可以想象可以ExecutionContext
使用操作系统线程,但在运行时会存在诸如 GC 之类的风险),这可能是一个带有全局线程的事件循环)。
调用线程在第 5 步执行之前被阻塞,所以从调用者的角度来看,它是非阻塞的,但是某处有一个线程被阻塞。
val asynAndNotBlocking: Future[HttpResponse] = Future { someNonBlockingHttpClient.get("baz.com") }
...可能不会进行类型检查(假设它与HttpResponse
上面的类型相同),因为为了非阻塞,HttpResponse
必须将其包装在表示异步/非阻塞的类型中(例如Future
),所以asyncAndNotBlocking
是type Future[Future[HttpResponse]]
,在一些特定用例之外,这是一种毫无意义的类型。你更有可能拥有类似的东西:
val asyncAndNotBlocking: Future[HttpResponse] = someNonBlockingHttpClient.get("baz.com")
或者,如果someNonBlockingHttpClient
不是 Scala 原生并返回一些其他异步库,您将拥有
val asyncAndNotBlocking: Future[HttpResponse] = SomeConversionToFuture(someNonBlockingHttpClient.get("baz.com"))
SomeConversionToFuture
基本上就像上面的草图Future.apply
,但是可以,而不是使用ExecutionContext
其他异步库中的使用操作来运行代码以在完成Future
时.get
完成关联。
如果您Future[Future[HttpResponse]]
出于某种原因真的想要,考虑到它可能someNonBlockingHttpClient
会很快返回.get
(请记住,它是异步的,因此它可以早在请求被设置并安排发送时返回),Future.apply
可能不是要走的路关于事情:步骤1-5的开销可能比花费的时间更长.get
!对于这种情况,Future.successful
很有用:
val doubleWrapped: Future[Future[HttpResponse]] = Future.successful( someNonBlockingHttpClient.get("baz.com"))
Future.successful
不涉及 thunk、创建Promise
或安排任务ExecutionContext
(它甚至不使用ExecutionContext
!)。它直接构造一个已经成功完成的Future
,但其中包含的值Future
是在调用之前计算的(即执行 thunk 中的内容)Future.successful
并阻塞调用线程。如果有问题的代码阻塞了足够长的时间来设置异步执行的东西,这不是问题,但是它可以使长时间阻塞的东西看起来像异步/非阻塞一样。
知道什么时候使用Future.apply
和Future.successful
是很重要的,特别是如果你关心性能和可伸缩性。看到Future.successful
使用不当可能比Future.apply
(因为它不需要隐含的ExecutionContext
,我已经看到新手被它所吸引)更常见。正如 Colin Breck 所说,不要通过不当使用Future.successful
.
推荐阅读
- json - 将嵌套 JSON 转换为 pandas DataFrame
- javascript - 替换 HTML 表格中的值?
- android - Android中服务类的结构 - 问题和检查工作
- c# - Instagram API - Instasharper 在登录时给出“challenge_required”错误
- sql - 在 sql 过程中更新表的 IF ELSE 语句不起作用
- codemirror - codemirror 内部模式自动缩进问题
- javascript - React 状态存储和输出重复值
- c - 当我访问结构的成员时发生分段错误
- node.js - Nuxt - 服务器端的 Axios 请求不发送referer
- python - 为什么我的模型的“on_delete=models.CASCADE”不生成级联外键约束?