自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么我放棄使用 Kotlin 中的協(xié)程?

開發(fā) 前端
實不相瞞,我對 Kotlin 這門編程語言非常喜歡,盡管它有一些缺點和奇怪的設計選擇。我曾經(jīng)參與過一個使用 Kotlin、Kotlin 協(xié)程(coroutine, 下同)和基于協(xié)程的服務器框架 KTOR 的中型項目。

 實不相瞞,我對 Kotlin 這門編程語言非常喜歡,盡管它有一些缺點和奇怪的設計選擇。我曾經(jīng)參與過一個使用 Kotlin、Kotlin 協(xié)程(coroutine, 下同)和基于協(xié)程的服務器框架 KTOR 的中型項目。這個技術組合提供了很多優(yōu)點,但是我也發(fā)現(xiàn),與常規(guī)的 Spring Boot 相比,它們很難使用。

[[330673]]

聲明:我無意抨擊相關技術,我的目的僅是分享我的使用體驗,并解釋為什么我以后不再考慮使用。

調(diào)試

請看下面一段代碼。

  1. suspend fun retrieveData(): SomeData { 
  2.     val request = createRequest() 
  3.     val response = remoteCall(request) 
  4.     return postProcess(response) 
  5.  
  6. private suspend fun remoteCall(request: Request): Response { 
  7.    // do suspending REST call 

假設我們要調(diào)試 retrieveData 函數(shù),可以在第一行中放置一個斷點。然后啟動調(diào)試器(我使用的是 IntelliJ),它在斷點處停止?,F(xiàn)在我們執(zhí)行一個 Step Over(跳過調(diào)用 createRequest),這也正常。但是如果再次 Step Over,程序就會直接運行,調(diào)用 remoteCall() 之后不會停止。

為什么會這樣?JVM 調(diào)試器被綁定到一個 Thread 對象上。當然,這是一個非常合理的選擇。然而,當引入?yún)f(xié)程之后,一個線程不再做一件事。仔細一看:remoteCall(request) 調(diào)用的是一個 suspend 函數(shù),雖然我們在調(diào)用它的時候并沒有在語法中看到它。那么會發(fā)生什么?我們執(zhí)行調(diào)試器 "step over ",調(diào)試器運行 remoteCall 的代碼并等待。

這就是難點所在:當前線程(我們的調(diào)試器被綁定到該線程)只是我們的coroutine 的執(zhí)行者。當我們調(diào)用 suspend 函數(shù)時,會發(fā)生的情況是,在某個時刻,suspend 函數(shù)會 yield。這意味著另外一個 Thread 將繼續(xù)執(zhí)行我們的方法。我們有效地欺騙了調(diào)試器。

我發(fā)現(xiàn)的唯一的解決方法是在我想執(zhí)行的行上放置一個斷點,而不是使用Step Over。不用說,這是個大麻煩。而且很顯然,這不僅僅是我一個人的問題。

此外,在一般的調(diào)試中,很難確定一個單一的 coroutine 當前在做什么,因為它在線程之間跳躍。當然,coroutine 是有名字的,你可以在日志中不僅打印線程,還可以打印 coroutine 的名字,但根據(jù)我的經(jīng)驗,調(diào)試基于 coroutine 的代碼所需的心智負擔,要比基于線程的代碼高很多。

REST 調(diào)用中綁定 context 數(shù)據(jù)

在微服務上開發(fā),一個常見的設計模式是,接收一個某種形式認證的 REST 調(diào)用,并將相同的認證傳遞給其他微服務的所有內(nèi)部調(diào)用。在最簡單的情況下,我們至少要保留調(diào)用者的用戶名。

然而,如果這些對其他微服務的調(diào)用在我們調(diào)用棧中嵌套了 10 層深度怎么辦?我們當然不希望在每個函數(shù)中都傳遞一個認證對象作為參數(shù)。我們需要某種形式的 "context",這種 context 是隱性存在的。

在傳統(tǒng)的基于線程的框架中,如 Spring,解決這個問題的方法是使用 ThreadLocal 對象。這使得我們可以將任何一種數(shù)據(jù)綁定到當前線程。只要一個線程對應一個 REST 調(diào)用(你應該始終以這個為目標),這正是我們需要的。這個模式的很好的例子是 Spring 的 SecurityContextHolder。

對于 coroutine,情況就不同了。一個 ThreadLocal 不再對應一個協(xié)程,因為你的工作負載會從一個線程跳到另一個線程;不再是一個線程在其整個生命周期內(nèi)伴隨一個請求。在 Kotlin coroutine 中,有 CoroutineContext。本質(zhì)上,它不過是一個 HashMap,與 coroutine 一起攜帶(無論它運行在哪個線程上)。它有一個可怕的過度設計的 API,使用起來很麻煩,但這不是這里的主要問題。

真正的問題是,coroutine 不會自動繼承上下文。

例如:

  1. suspend fun sum(): Int { 
  2.     val jobs = mutableListOf<Deferred<Int>>() 
  3.     for(child in children){ 
  4.         jobs += async {  // we lose our context here! 
  5.             child.evaluate()  
  6.         } 
  7.     } 
  8.     return jobs.awaitAll().sum() 

每當你調(diào)用一個 coroutine builder,如 async、runBlocking 或 launch,你將(默認情況下)失去你當前的 coroutine 上下文。你可以通過將上下文顯式地傳遞到 builder 方法中來避免這種情況,但是上帝保佑你不要忘記這樣做(編譯器不會管這些!)。

一個子 coroutine 可以從一個空的上下文開始,如果有一個上下文元素的請求進來,但沒有找到任何東西,可以向父 coroutine 上下文請求該元素。然而,在 Kotlin 中不會發(fā)生這種情況,開發(fā)人員需要手動完成,每一次都是如此。

如果你對這個問題的細節(jié)感興趣,我建議你看看這篇博文。

https://blog.tpersson.io/2018/04/22/emulating-request-scoped-objects-with-kotlin-coroutines/

synchronized 不再如你想的那樣工作

在 Java 中處理鎖或 synchronized 同步塊時,我考慮的語義通常是 "當我在這個塊中執(zhí)行時,其他調(diào)用不能進入"。當然“其他調(diào)用”意味著存在某種身份,在這里就是線程,這應該在你的腦海中升起一個大紅色的警告信號。

看看下面的例子。

  1. val lock = ReentrantLock() 
  2.  
  3. suspend fun doWithLock(){ 
  4.    lock.withLock { 
  5.        callSuspendingFunction() 
  6.    } 

這個調(diào)用很危險,即使 callSuspendingFunction() 沒有做任何危險的操作,代碼也不會像你想象的那樣工作。

  • 進入同步鎖
  • 調(diào)用 suspend 功能
  • 協(xié)程 yield,當前線程仍然持有鎖。
  • 另一個線程繼續(xù)我們的 coroutine
  • 還是同一個協(xié)程,但我們不再是鎖的 owner 了!

潛在的沖突、死鎖或其他不安全的情況數(shù)量驚人。你可能會說,我們只是需要設計我們的代碼來處理這個問題。我同意,然而我們談論的是 JVM。那里有一個龐大的 Java 庫生態(tài)。而它們并沒有做好處理這些情況的準備。

這里的結(jié)果是:當你開始使用 coroutine 的時候,你就放棄了使用很多 Java 庫的可能性,因為它們目前只能工作在基于線程的環(huán)境。

單機吞吐量與水平擴展

對于服務器端來說,coroutine 的一大優(yōu)勢是,一個線程可以處理更多的請求;當一個請求等待數(shù)據(jù)庫響應時,同一個線程可以愉快地服務另一個請求。特別是對于 I/O 密集型任務,這可以提高吞吐量。

然而,正如這篇博文所希望向您展示的那樣,在許多層面上,使用 coroutine 都有一個非零成本的開銷。

由此產(chǎn)生的問題是:這個收益是否值得這個成本?而在我看來,答案是否定的。在云和微服務環(huán)境中,有一些現(xiàn)成的擴展機制,無論是 Google AppEngine、AWS Beanstalk 還是某種形式的 Kubernetes。如果當前負載增加,這些技術將簡單地按需生成你的微服務的新實例。因此,考慮到引入 coroutine 帶來的額外成本,單一實例所能處理的吞吐量就不那么重要了。這就降低了我們使用 coroutine 所獲得的價值。

Coroutine 有其存在的價值

話說回來,Coroutine 還是有其使用場景。當開發(fā)只有一個 UI 線程的客戶端 UI 時,coroutine 可以幫助改善你的代碼結(jié)構,同時符合 UI 框架的要求。聽說這個在安卓系統(tǒng)上很好用。Coroutine 是一個有趣的主題,然而對于服務器端開發(fā)來說,我覺得協(xié)程還差點意思。JVM 開發(fā)團隊目前正在開發(fā) Fiber,本質(zhì)上也是 coroutine,但他們的目標是與 JVM 基礎庫更好共存。這將是有趣的,看它將來如何發(fā)展,以及 Jetbrains 對 Kotlin coroutine 對此會有什么反應。在最好的情況下,Kotlin coroutine 將來只是簡單映射到 Fiber 上,而調(diào)試器也能足夠聰明來正確處理它們。

英文原文:

https://dev.to/martinhaeusler/why-i-stopped-using-coroutines-in-kotlin-kg0

本文轉(zhuǎn)載自微信公眾號「高可用架構」,可以通過以下二維碼關注。轉(zhuǎn)載本文請聯(lián)系高可用架構公眾號。

 

責任編輯:武曉燕 來源: 高可用架構
相關推薦

2023-10-24 19:37:34

協(xié)程Java

2019-10-23 14:34:15

KotlinAndroid協(xié)程

2023-07-23 17:19:34

人工智能系統(tǒng)

2021-05-20 09:14:09

Kotlin協(xié)程掛起和恢復

2020-08-14 10:40:35

RestTemplatRetrofitJava

2020-03-03 15:31:47

ReactVue前端

2021-02-01 07:20:51

KafkaPulsar搜索

2022-10-28 10:45:22

Go協(xié)程GoFrame

2020-02-19 14:16:23

kotlin協(xié)程代碼

2011-06-08 10:30:08

MongoDB

2018-12-21 11:26:49

MySQLMongoDB數(shù)據(jù)庫

2021-04-28 09:08:23

Kotlin協(xié)程代碼

2020-07-23 08:07:47

數(shù)組upData庫函數(shù)

2021-09-16 09:59:13

PythonJavaScript代碼

2025-02-28 09:04:08

2021-04-25 09:36:20

Go協(xié)程線程

2023-11-17 11:36:59

協(xié)程纖程操作系統(tǒng)

2017-10-23 12:42:42

2023-07-30 23:44:49

Go協(xié)程進程

2019-09-17 15:30:13

Java編程語言
點贊
收藏

51CTO技術棧公眾號