[英]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") }
我的問題是:
val syncAndBlocking: HttpResponse = someHttpClient.get("foo.com")
這將阻塞調用線程,直到收到響應(如您所見)。
val asyncButBlocking: Future[HttpResponse] = Future { someHttpClient.get("bar.com") }
與對Future.apply
任何調用Future.apply
,這(至少在概念上,可能會進行優化以消除某些步驟):
Function0[HttpResponse]
(為了簡潔,我將其稱為 thunk),其apply
方法是someHttpClient.get("bar.com")
。 如果someHttpClient
是靜態的,理論上這可能在編譯時發生(我不知道 Scala 編譯器是否會執行此優化),否則它將在運行時發生。Future.apply
,然后:Promise[HttpResponse]
。Future.apply
的ExecutionContext
上安排任務。 這個任務就是調用thunk:如果thunk成功執行, Promise
的關聯Future
就用thunk的結果完成(成功),否則Future
失敗(也是一個完成),拋出的Throwable
(可能只會失敗)如果Throwable
與NonFatal
匹配,我懶得檢查,在這種情況下, ExecutionContext
會捕獲致命的拋出)。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
),所以asyncAndNotBlocking
是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
使用其他異步庫中的操作來運行代碼以在.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.apply
和Future.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.