简体   繁体   English

Scala Futures 如何在线程上运行? 以及如何使用它们来执行异步和非阻塞代码?

[英]How do Scala Futures operate on threads? And how can they be used to execute async & non-blocking code?

To my understanding, there are the 3 ways of doing IO in Scala, which I will try to express in pesudo code.据我了解,Scala 中有 3 种执行 IO 的方式,我将尝试用伪代码来表达。

First , synchronous & blocking:、同步&阻塞:

val syncAndBlocking: HttpResponse = someHttpClient.get("foo.com")

Here the main thread is just idle until the response is back..这里主线程只是空闲,直到响应回来..

Second , async but still blocking:其次,异步但仍然阻塞:

val asyncButBlocking: Future[HttpResponse] = Future { someHttpClient.get("bar.com") }

To my understanding, here the main thread is free (as Future executes on a separate thread) but that separate thread is blocked..据我了解,这里的主线程是空闲的(因为 Future 在一个单独的线程上执行)但是那个单独的线程被阻塞了..

Third , asynchronous & non blocking.第三,异步和非阻塞。 I am not sure how to implement that one, but to my best guess the implementation (eg. http client) itself has to be non-blocking, so something like:我不确定如何实现那个,但据我所知,实现(例如 http 客户端)本身必须是非阻塞的,例如:

val asynAndNotBlocking: Future[HttpResponse] = Future { someNonBlockingHttpClient.get("baz.com") }

My questions are:我的问题是:

  1. Are my aforementioned assumptions valid?我的上述假设有效吗?
  2. Do Scala futures run on OS Threads or green threads? Scala 期货是在 OS 线程还是绿色线程上运行? Or that depends on the execution context?或者这取决于执行上下文?
  3. In the third case where IO is async & non-blocking, how does that work under the hood?在 IO 是异步和非阻塞的第三种情况下,它是如何在后台工作的? Does the thread only start the task (eg. send get request), and then becomes free again until it gets notified by some sort of an event loop when the response has arrived?线程是否只启动任务(例如发送获取请求),然后再次空闲,直到响应到达时通过某种事件循环通知它?

Question inspired by the following references: here & here受以下参考资料启发的问题:此处此处

val syncAndBlocking: HttpResponse = someHttpClient.get("foo.com")

This will block the calling thread until the response is received (as you note).这将阻塞调用线程,直到收到响应(如您所见)。

val asyncButBlocking: Future[HttpResponse] = Future { someHttpClient.get("bar.com") }

As with any call to Future.apply , this (at least conceptually, there may be optimizations which eliminate some steps):与对Future.apply任何调用Future.apply ,这(至少在概念上,可能会进行优化以消除某些步骤):

  1. Creates a Function0[HttpResponse] (I'll call it a thunk, for brevity) whose apply method is someHttpClient.get("bar.com") .创建一个Function0[HttpResponse] (为了简洁,我将其称为 thunk),其apply方法是someHttpClient.get("bar.com") If someHttpClient is static, this could theoretically happen at compile time (I don't know off the top of my head if the Scala compiler will perform this optimization), otherwise it will happen at runtime.如果someHttpClient是静态的,理论上这可能在编译时发生(我不知道 Scala 编译器是否会执行此优化),否则它将在运行时发生。
  2. Passes that thunk to Future.apply , which then:将那个 thunk 传递给Future.apply ,然后:
  3. Creates a Promise[HttpResponse] .创建一个Promise[HttpResponse]
  4. Schedules a task on the ExecutionContext passed implicitly to Future.apply .在隐式传递给Future.applyExecutionContext上安排任务。 This task is to call the thunk: if the thunk successfully executes, the Promise 's associated Future is completed (successfully) with the result of the thunk, otherwise that Future fails (also a completion) with the thrown Throwable (it may only fail if the Throwable is matched by NonFatal , I'm too lazy to check, in which case fatal throws are caught by the ExecutionContext ).这个任务就是调用thunk:如果thunk成功执行, Promise的关联Future就用thunk的结果完成(成功),否则Future失败(也是一个完成),抛出的Throwable (可能只会失败)如果ThrowableNonFatal匹配,我懒得检查,在这种情况下, ExecutionContext会捕获致命的抛出)。
  5. As soon as the task is scheduled on the ExecutionContext (which may or may not be before the thunk is executed), the Future associated with the Promise is returned.一旦在ExecutionContext上安排了任务(可能在也可能不在 thunk 执行之前),就会返回与Promise关联的Future

The particulars of how the thunk is executed depend on the particular ExecutionContext and by extension on the particulars of the Scala runtime (for Scala on the JVM, the thunk will be run on a thread determined by the ExecutionContext , whether this is an OS thread or a green thread depends on the JVM, but OS thread is probably a safe assumption at least for now (Project Loom may affect that); for ScalaJS (since JavaScript doesn't expose threads) and Scala Native (as far as I know for now: conceivably an ExecutionContext could use OS threads, but there would be risks in the runtime around things like GC), this is probably an event loop with a global thread).如何执行 thunk 的细节取决于特定的ExecutionContext以及 Scala 运行时的细节(对于 JVM 上的 Scala,thunk 将在ExecutionContext确定的线程上ExecutionContext ,无论这是操作系统线程还是绿色线程取决于 JVM,但 OS 线程至少目前可能是一个安全的假设(Project Loom 可能会影响到这一点);对于 ScalaJS(因为 JavaScript 不公开线程)和 Scala Native(据我目前所知) :可以想象,一个ExecutionContext可以使用操作系统线程,但是在运行时会存在诸如 GC 之类的风险),这可能是一个具有全局线程的事件循环)。

The calling thread is blocked until step 5 has executed, so from the caller's perspective, it's non-blocking, but there's a thread somewhere which is blocked.调用线程被阻塞,直到第 5 步执行完毕,所以从调用者的角度来看,它是非阻塞的,但是某处有一个线程被阻塞。

val asynAndNotBlocking: Future[HttpResponse] = Future { someNonBlockingHttpClient.get("baz.com") }

...is probably not going to typecheck (assuming that it's the same HttpResponse type as above) since in order to be non-blocking the HttpResponse would have to be wrapped in a type which denotes asynchronicity/non-blocking (eg Future ), so asyncAndNotBlocking is of type Future[Future[HttpResponse]] , which is kind of a pointless type outside of a few specific usecases. ...可能不会进行类型检查(假设它与上面的HttpResponse类型相同),因为为了非阻塞, HttpResponse必须包装在表示异步/非阻塞的类型中(例如Future ),所以asyncAndNotBlockingFuture[Future[HttpResponse]]类型,在一些特定用例之外,这是一种毫无意义的类型。 You'd be more likely to have something like:你更有可能有这样的事情:

val asyncAndNotBlocking: Future[HttpResponse] = someNonBlockingHttpClient.get("baz.com")

or, if someNonBlockingHttpClient isn't native to Scala and returns some other asynchrony library, you'd have或者,如果someNonBlockingHttpClient不是 Scala 原生的并返回一些其他异步库,您将拥有

val asyncAndNotBlocking: Future[HttpResponse] = SomeConversionToFuture(someNonBlockingHttpClient.get("baz.com"))

SomeConversionToFuture would basically be like the sketch above of Future.apply , but could, instead of using an ExecutionContext use operations in that other asynchrony library to run code to complete the associated Future when .get completes. SomeConversionToFuture基本上就像上面的Future.apply草图,但可以,而不是使用ExecutionContext使用其他异步库中的操作来运行代码以在.get完成时完成关联的Future

If you really wanted Future[Future[HttpResponse]] for some reason, given that it's likely that someNonBlockingHttpClient will return very quickly from .get (remember, it's asynchronous, so it can return as early as the request being set up and scheduled for being sent), Future.apply is probably not the way to go about things: the overhead of steps 1-5 may take longer than the time spent in .get !如果出于某种原因你真的想要Future[Future[HttpResponse]] ,考虑到someNonBlockingHttpClient很可能会从.get非常快地返回(记住,它是异步的,所以它可以在请求被设置和安排的时候尽早返回发送), Future.apply可能不是解决问题的方法:步骤 1-5 的开销可能比.get花费的时间更长! For this sort of situation, Future.successful is useful:对于这种情况, Future.successful很有用:

val doubleWrapped: Future[Future[HttpResponse]] = Future.successful( someNonBlockingHttpClient.get("baz.com"))

Future.successful doesn't involve a thunk, create a Promise , or schedule a task on the ExecutionContext (it doesn't even use an ExecutionContext !). Future.successful不涉及 thunk、创建Promise或在ExecutionContext上安排任务(它甚至不使用ExecutionContext !)。 It directly constructs an already-successfully-completed Future , but the value contained in that Future is computed (ie what would be in the thunk is executed) before Future.successful is called and it blocks the calling thread.它直接构造了一个已经成功完成的Future ,但是在Future.successful被调用之前计算了包含在该Future的值(即执行 thunk 中的值)并且它阻塞了调用线程。 This isn't a problem if the code in question is blocking for just long enough to setup something to execute asynchronously, but it can make something that's blocking for a long time look to the outside world like it's async/non-blocking.如果有问题的代码阻塞了足够长的时间来设置异步执行,这不是问题,但是它可以使长时间阻塞的东西看起来像异步/非阻塞一样。

Knowing when to use Future.apply and Future.successful is of some importance, especially if you care about performance and scalability.知道什么时候使用Future.applyFuture.successful是很重要的,特别是如果你关心性能和可伸缩性。 It's probably more common to see Future.successful used inappropriately than Future.apply (because it doesn't require an implicit ExecutionContext , I've seen novices gravitate to it).看到Future.successful使用不当可能比Future.apply更常见(因为它不需要隐式ExecutionContext ,我见过新手被它Future.apply )。 As Colin Breck put it, don't block your future success by improperly using Future.successful .正如 Colin Breck 所说, 不要通过不当使用Future.successful 阻碍您未来的成功

A Future doesn't ”execute“ anywhere, because it is not a piece of runnable code – it is merely a way to access a result that may or may not yet be available. Future 不会在任何地方“执行”,因为它不是一段可运行的代码——它只是一种访问可能可用或可能不可用的结果的方法。

The way you create a Future is by creating a Promise and then calling the future method on it.创建Future的方式是创建一个Promise ,然后在其上调用future方法。 The Future will be completed as soon as you call the complete method on the Promise object.一旦调用 Promise 对象的complete方法,Future 就会完成。

When you create a Future using Future { doStuff() } , what happens is that a Promise is created, then doStuff starts executing in the ExecutionContext – usually that means it's running in a different thread.当你使用Future { doStuff() }创建一个 Future 时,会发生一个 Promise 被创建,然后doStuff开始在ExecutionContext ——通常这意味着它在不同的线程中运行。 Then .future is called on the Promise and the Future is returned.然后在 Promise 上调用.future并返回 Future。 When doStuff is done, complete is called on the Promise.doStuff完成时,在 Promise 上调用complete

So conceptually, Futures and threads/ExecutionContexts are independent – there doesn't need to be a thread (green or otherwise) doing stuff for every incomplete future.所以从概念上讲,Futures 和线程/ExecutionContexts 是独立的——不需要有一个线程(绿色或其他)为每个不完整的 Future 做事。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM