簡體   English   中英

Scala Futures 如何在線程上運行? 以及如何使用它們來執行異步和非阻塞代碼?

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

據我了解,Scala 中有 3 種執行 IO 的方式,我將嘗試用偽代碼來表達。

、同步&阻塞:

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") }

我的問題是:

  1. 我的上述假設有效嗎?
  2. Scala 期貨是在 OS 線程還是綠色線程上運行? 或者這取決於執行上下文?
  3. 在 IO 是異步和非阻塞的第三種情況下,它是如何在后台工作的? 線程是否只啟動任務(例如發送獲取請求),然后再次空閑,直到響應到達時通過某種事件循環通知它?

受以下參考資料啟發的問題:此處此處

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

這將阻塞調用線程,直到收到響應(如您所見)。

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

與對Future.apply任何調用Future.apply ,這(至少在概念上,可能會進行優化以消除某些步驟):

  1. 創建一個Function0[HttpResponse] (為了簡潔,我將其稱為 thunk),其apply方法是someHttpClient.get("bar.com") 如果someHttpClient是靜態的,理論上這可能在編譯時發生(我不知道 Scala 編譯器是否會執行此優化),否則它將在運行時發生。
  2. 將那個 thunk 傳遞給Future.apply ,然后:
  3. 創建一個Promise[HttpResponse]
  4. 在隱式傳遞給Future.applyExecutionContext上安排任務。 這個任務就是調用thunk:如果thunk成功執行, Promise的關聯Future就用thunk的結果完成(成功),否則Future失敗(也是一個完成),拋出的Throwable (可能只會失敗)如果ThrowableNonFatal匹配,我懶得檢查,在這種情況下, ExecutionContext會捕獲致命的拋出)。
  5. 一旦在ExecutionContext上安排了任務(可能在也可能不在 thunk 執行之前),就會返回與Promise關聯的Future

如何執行 thunk 的細節取決於特定的ExecutionContext以及 Scala 運行時的細節(對於 JVM 上的 Scala,thunk 將在ExecutionContext確定的線程上ExecutionContext ,無論這是操作系統線程還是綠色線程取決於 JVM,但 OS 線程至少目前可能是一個安全的假設(Project Loom 可能會影響到這一點);對於 ScalaJS(因為 JavaScript 不公開線程)和 Scala Native(據我目前所知) :可以想象,一個ExecutionContext可以使用操作系統線程,但是在運行時會存在諸如 GC 之類的風險),這可能是一個具有全局線程的事件循環)。

調用線程被阻塞,直到第 5 步執行完畢,所以從調用者的角度來看,它是非阻塞的,但是某處有一個線程被阻塞。

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

...可能不會進行類型檢查(假設它與上面的HttpResponse類型相同),因為為了非阻塞, HttpResponse必須包裝在表示異步/非阻塞的類型中(例如Future ),所以asyncAndNotBlockingFuture[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使用其他異步庫中的操作來運行代碼以在.get完成時完成關聯的Future

如果出於某種原因你真的想要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.successful被調用之前計算了包含在該Future的值(即執行 thunk 中的值)並且它阻塞了調用線程。 如果有問題的代碼阻塞了足夠長的時間來設置異步執行,這不是問題,但是它可以使長時間阻塞的東西看起來像異步/非阻塞一樣。

知道什么時候使用Future.applyFuture.successful是很重要的,特別是如果你關心性能和可伸縮性。 看到Future.successful使用不當可能比Future.apply更常見(因為它不需要隱式ExecutionContext ,我見過新手被它Future.apply )。 正如 Colin Breck 所說, 不要通過不當使用Future.successful 阻礙您未來的成功

Future 不會在任何地方“執行”,因為它不是一段可運行的代碼——它只是一種訪問可能可用或可能不可用的結果的方法。

創建Future的方式是創建一個Promise ,然后在其上調用future方法。 一旦調用 Promise 對象的complete方法,Future 就會完成。

當你使用Future { doStuff() }創建一個 Future 時,會發生一個 Promise 被創建,然后doStuff開始在ExecutionContext ——通常這意味着它在不同的線程中運行。 然后在 Promise 上調用.future並返回 Future。 doStuff完成時,在 Promise 上調用complete

所以從概念上講,Futures 和線程/ExecutionContexts 是獨立的——不需要有一個線程(綠色或其他)為每個不完整的 Future 做事。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM