Kotlin協程的那些事兒

校對:欣亦||排版:餘薇java


Kotlin 協程如今愈來愈成熟,也已經很是適合運用於生產環境當中,但對於Kotlin協程設計,你是否也有一些困惑呢?linux


剛剛結束的Android 11 Meetup第二期直播中,經過騰訊地圖平臺部高級工程師,Kotlin佈道師、Kotlin中文社區負責人,《深刻理解Kotlin協程》做者,慕課網《Kotlin入門到精通》講師霍丙乾,爲你們帶來的精彩分享《Kotlin協程的那些事兒》,你或許有所收穫。web


錯過直播的小夥伴彆着急,小編已經整理出詳盡回顧內容,點擊文末【閱讀原文】,還能夠獲取直播回放哦~編程


嘉賓分享windows


1.協程究竟是什麼安全




協程究竟是什麼?通常來講你們遇到這個問題的話怎麼辦?先去維基百科上面去搜一下什麼叫協程?維基百科上面的答案給出來,你們看完了以後保證仍是看不懂。服務器


他說什麼子例程什麼之類的,你看了以後你直接暈菜。其實沒那麼複雜,通常說子歷程這個概念爲何我們比較陌生,由於我們經常使用的這些編程語言,好比說Java,裏面沒有這個概念,你就把它想成一個函數就好了。

咱們的函數拿出來了以後,這個協程到底基於函數作了哪些手腳?他無非就是能夠支持掛起跟恢復,並且掛起恢復確定有人又問了,線程也能夠,線程也有掛起跟恢復,爲何協程跟線程就不同?由於線程的掛起跟恢復是操做系統來決定的。協程它掛起恢復不同的點在於,是咱們在用戶層面程序開發者他本身就能夠決定掛起跟恢復。

       


協程的做用
微信


一、協程可讓異步代碼同步化,下降異步程序的設計複雜度併發


協程是幹什麼用的?你要追溯到很早操做系統的設計,那個時候CPU剛設計出來,它性能沒有那麼好,最開始的時候可能就想着電腦設計出來就讓他執行一個任務就完了,但執行一個任務確定不行,電腦打開了以後又想聽歌,又想看直播,還想打遊戲, 還要看一下微信的消息,一個CPU若是隻能執行一個任務的話確定不行。

怎麼辦?CPU執行權到底要怎麼分配呢?其實就想出了比較直接的兩個方案,線程的話就是把CPU的執行的時間分紅片,時間片用完了以後,根據必定的策略,好比說搶佔式調度,若是一個線程可能執行須要5秒鐘,那麼一次執行我只給你100毫秒,執行完了以後把執行權交出來給別人執行。

這個時間片若是劃分的足夠小的話,那麼一個單核單線程的CPU,其實它整個能給人的一個感受就好像一直在併發同樣。那麼除了線程這種搶佔式的調度以外,其實也能夠採用協程這種方式。協程跟線程不同的就是,一個任務執行的差很少了,就讓出CPU來交給其餘的任務。

             

什麼叫異步?就是執行了以後也立刻執行,執行權切給別人了,等他執行完了以後再給你,這就是異步。這個邏輯很難去寫得特別的漂亮,由於你首先就不是連續的。咱們很直觀的就能看獲得,他比同步要複雜。因此協程由於能夠本身控制掛起跟恢復,這塊他就能夠把異步的邏輯同步形式,你看到它是同步代碼,但實際上它是異步的邏輯,協程能夠作到這一點。

異步簡化就是異步同步化,我但願IO操做能夠自動的切換到IO線程當中,那麼返回結果再回到我原來執行的線程當中,這代碼就看起來很是的舒服了。

              

二、協程能夠實現輕量級的併發,提升系統資源的利用率app


實現輕量級的併發,提升系統資源的利用率,這一塊跟前面的異步也是有關係的。其實咱們用線程的話,並非寫不出來,只是線程要用這種異步IO寫出來的代碼會很是的醜,那麼協程由於能夠把異步代碼同步化,同時協程佔用的資源又不多,切換的開銷又不多,因此這塊就能夠實現輕量級的變化。

協程的演進

            

Kotlin協程的演進這塊也頗有意思,剛纔提到說1.1當時發佈的時候框架還不成熟,框架就是kotlinx.coroutines,他其實提供了很是豐富的API讓咱們使用,當時1.1剛發出來的時候就是一個標準庫,標準庫裏邊有上下文、攔截器、掛起函數,其實到真正1.3最後正式發佈的時候,標準庫裏邊的改動都很小,最明顯的改動就是把experimental那個包名給去掉了。從1.3開始,框架已經成熟了,安卓開發者想使用就能夠把Kotlinx.coroutines的構件引進來,它裏面提供了好比說調度器、Job、做用域的支持。


2.爲何大多數開發者以爲協程學起來很難?



咱們學Kotlin的時候一個重要的難點就是協程,除了協程以外,最大的難點應該就是函數,其實函數是個數學概念,並非很難,只不過看起來很抽象,感受很難,其實只是須要一個接受的過程。因此剛開始接觸的時候,由於你沒有這方面的背景,你要接觸的東西不少,當你把這些概念全都吃透以後,就發現很是簡單了。

還有線程,不少開發者以爲本身理解了,但實際上你們對線程的理解其實並無真正達到一箇中等偏上的一個程度。由於不少時候線程你們只是會用,你要真的想把它用好的話,這些併發安全的問題全都得要解決掉。這就是爲何線程你們學習的時候比協程感受要容易一些,緣由在於咱們剛纔說了線程其實操做系統幫咱們作了不少的事,他的掛起也好,執行也好,咱們其實不須要操心的,操做系統均可以本身悄悄的把這個問題給解決掉了,以致於咱們以爲線程很簡單,其實是很難的。當你遇到了併發安全的問題的時候,你就以爲這事並無那麼簡單。


3.Kotlin協程的設計比其餘語言的複雜,爲何?



協程1.3剛剛發佈的時候,當時在Kotlin羣裏邊有一個哥們就直接開始狂噴Kotlin協程,說設計很是不友好,他就舉個js的例子,async/await除了js還有誰支持?實際上 C#應該是最先支持的,如今包括Dart、Python都支持了。async/await爲何受到了這麼多語言的歡迎?由於它設計出來以後,開發者能夠直接去用它,並且你也不用太關心它背後到底發生了什麼。

              

咱們來看一下這些語言,他們的協程設計出來以後到底能幹什麼。好比說,Lua的協程在講述的時候就會至關於它的線程,其實在運行的過程當中它就是經過協程來處理併發的,只不過這個併發須要咱們本身考慮掛起跟恢復,跟咱們在Java當中線程來處理併發思路徹底不同;
GO當中引入了Go routine,這就是GO最大的亮點,GO出來以後對於併發的支持很是棒,Go routine爲何很火,他其實設計的跟Kotlin協程不同,他是一個有棧協程,就是調用棧,咱們的函數若是調用的層次比較多,會出現stack overflow的異常。

如今比較流行都是無棧協程,好比說async/await,好比kotlin當中的suspend這種,其實咱們均可以認爲它是無棧協程,由於他沒有專門去開闢一塊內存的空間來保存它的調用棧,可是Go routine 是有調用棧的。Go routine這塊優化很是的好,它調用棧通常來講就是4kb,並且能夠動態地局部地調整。在程序運行的時候,它的內存分佈有一個棧區和一個堆區,注意調用棧跟棧區不是一回事兒。一般來講,咱們的調用棧是分配在棧區上的,可是Go routine它的調用棧是分配的堆區,它就能夠自動的去擴容跟縮容。

             

接下來再來看一下js或者C#,他們其實都是拿出來,直接簡化異步就能夠了。Kotlin的話,咱們直接在標準庫裏邊搞了一個suspend的函數,它能夠經過框架的封裝,封裝出來async/await,也能夠經過框架的封裝出來, channel的話其實咱們稍微作點封裝就能夠封裝出來GO routine的效果,他就能夠去支持大量的任務併發。那麼Kotlin的協程在框架當中還封裝了一個flow,這個flow是響應式編程風格的一個API,我就能夠至關於相似於在Rxjava當中去使用掛起函數,至關因而這麼個做用。它其實本質上也是用來簡化異步的。

       


4.Kotlin協程爲何能夠簡化異步邏輯的寫法?



• 異步任務就是調用流程的切換

• 協程的協做性可以使得異步觸發點和返回點「拼接」起來

 

     

當多個請求多個結果的時候怎麼辦?跟剛纔js裏邊的async/await實際上是相似的,咱們的suspend關鍵字,若是你在定義函數的時候,前面加一個suspend,它相似於async/await裏面的async,你在調用它的時候至關於await是吧?既有掛起又有恢復,這個時候的話其實咱們獲得 user要掛起恢復的正常的結果,那麼這個異常的話就是掛起恢復的時候的異常的結果,對吧?這就是同步的關係。

@GET("users/{login}")suspend fun getUser(@Path("login") login: String): User


在retrofit當中,2.6版本以後,你能夠直接把它定義成一個suspend函數返回的結果,不要寫call了,也不要寫什麼observable,調用的時候就跟咱們同步函數調用的寫法是如出一轍的。若是你們是使用launch,什麼調度器都不傳的話,它會有一個默認的調度器,這個調度器其實背後是個線程,最後線程池,那麼執行過程當中會遇到什麼?我一開始的時候在線程1,try進來的時候,在線程一上執行,執行完後去請求,而後被調度到了一個IO的線程3上面,請求結果回來了,又調度到線程2上面。

對於咱們安卓開發者,不少時候需求場景是這個樣子的,我但願它是主線程跟IO線程的背景。在UI線程當中發請求的時候,就讓他去IO線程上執行,執行完以後再回到主線程上。因此Kotlin就能夠實現異步代碼的同步形式化。

       


5.爲何說Kotlin的協程比線程輕量?



爲何線程比較重?線程自打一出生他就會有一個調用棧,那這個棧到底要佔多少內存,咱們看一下:

             

咱們在不一樣平臺上默認的java虛擬機調用棧的大小,如今主要是64位,最小的都128k了,好比咱們一般用linux和windows。咱們再看一下安卓上佔多少,大概是一兆多點。咱們去跟代碼跟到底層的話,也能夠發現這一兆在哪裏。

             

注意這裏邊的代碼stack size,咱們在Java層去new thread的時候,是能夠傳一個調用棧大小的,若是你不傳的話,他會去虛擬機運行配置裏邊去找默認的值,但這個值跟一兆比起來它很小,因此咱們能夠看到他內存開銷就這麼大,是否是有點嚇人?

而Kotlin協程的內存開銷有多大呢?按照調用棧的定義,其實kotlin協程的實現應該算是一個無棧的協程。有人專門作過一個統計,經過launch啓動一個協程只佔幾十字節的內存。幾百kb是什麼概念?一兆至關於1024個Kb,1024個kb再乘10就是至關於一萬倍差,若是協程跟線程同時能解決的問題,用線程去解決的話,確定是比協程要重得多。


6.Kotlin協程究竟怎麼學?


            

kotlin協程它分層分出來是這個樣子,那麼咱們對於你們學習協程的要求是什麼樣的?業務代碼確定要熟練編寫、框架API要靈活的應用、標準庫要理解…不少同窗上來就想我要知道怎麼用,說實話你得先把背景搞明白,這東西能應用在異步場景當中,那麼異步是什麼?搞明白了嗎?你沒搞明白的話,你學的時候就會很痛苦。

那麼協程跟響應式編程兩個方向均可以提升服務器併發的性能,kotlin將來側重哪個,其實說實話,怎麼說kotlin這些東西都是框架層面,kotlin原生知識來協程,那麼又經過協程的API又支持了flow,其實就是響應式編程的一個API了。你說kotlin側重哪一個方向呢?其實我以爲確定是側重協程,由於協程寫出來確定比響應式的代碼要更直觀,更容易理解。


Q&A環節


 

Q1.若是存在多個協程並行,其中一個出讓執行權後,哪個協程會接收執行權?是如何判斷的?

 

A:咱們剛纔提到過,協程能夠分爲有棧和無棧,其實協程的分類還有一個維度,叫作對稱和非對稱式的調度。對稱的調度就是意味着全部的協程都是對等的,好比Go routine,當其中一個協程出讓執行權的時候,誰來接收就取決於你中間的橋樑,好比我向Channel裏面寫數據,哪一個協程來讀這個數據,就去接受這個調度權。對於非對稱調度,好比說a調用b,就像一個函數調用同樣,那麼b在返回執行權的時候,就像函數返回同樣,誰調用的你就返回給誰。因此,多個協程的調度,徹底取決於你的設計以及調動的方式。首先判斷是對稱的仍是非對稱的,若是是非對稱的話,誰調用你,你返回的時候就誰來接受。那麼在kotlin當中它其實是屬於非對稱式的調度,固然咱們也能夠經過kotlin協程的API來實現對稱式協程的效果,在《深刻理解kotlin協程》那本書的源碼裏邊能夠找到更爲具體的解答。

 

Q2.協程拋異常外層能catch到嗎?

 

A:協程拋異常其實是協程自己對異常處理的邏輯。在kotlin當中,對異常的處理要根據異常處理器Exception Handler的設置和跟做用域的關係,來判斷到底能不能捕獲到外層,捕獲時還須要看你對外層的定義,好比說,在一個協程外面啓動了另外一個協程,而後又調用了join,若是協程裏邊拋了異常,要想捕獲join,其實須要不少條件,好比裏邊的協程跟外邊的協程是否是同一個做用域,或者說裏邊協程是否是supervisor scope等等。因此異常的捕獲首先取決於外層的定義,第二個就是如何樣去捕獲它,再一個就是所謂的做用域的關係,以及你有沒有設置Exception Handler,這些都是會影響到你能不能捕獲到的。我以爲提問題的這位開發者能夠去看一下書裏邊的介紹,或者說閱讀官方文檔就能夠了。

 

學習渠道



  • Kotlin公衆號

  • GDG公衆號

  • 推薦書籍《深刻理解Kotlin協程》

  • 推薦書籍《Kotlin編程實戰》

  • kotlin 協程官方文檔和相關源碼

  • 慕課「從入門到精通」


直接閱讀源碼



  • Kotlin 官方協程框架源碼:kotlinx.coroutines

  • Kotlin 官方協程框架簡化仿寫版:CoroutineLite

  • 《深刻理解 Kotlin 協程》源碼:DiveIntoKotlinCoroutines-Sources


其餘資源



    • 官網:http://kotlinlang.org/

    官網(中文):https://www.kotlincn.net/

    • 博客:https://blog.jetbrains.com/kotlin

     博客(中文):https://www.kotliner.cn/

    論壇:https://discuss.kotlinlang.org/

    • 論壇(中文):https://discuss.kotliner.cn/


關於GDG

Google Developer Groups 谷歌開發者社區,是谷歌開發者部門發起的全球項目,面向對 Google 和開源技術感興趣的人羣而存在的公益性開發者社區。GDG Shanghai 創立於 2009 年,是全球 GDG 社區中最活躍和知名的技術社區之一,每一年舉辦 30 – 50 場大大小小的科技活動,每一年影響十幾萬以上海爲中心輻射長三角地帶的開發者及科技從業人員。

社區中的各位組織者均是來自各個行業有着本職工做的互聯網從業者,咱們須要更多新鮮血液的加入!若是你對谷歌技術感興趣,業餘時間可調配,認同社區的價值觀,願意爲社區作出貢獻,歡迎加入咱們成爲社區志願者!


志願者加入方式:關注上海 GDG 公衆號:GDG_Shanghai,回覆:志願者。

社區成員加入方式:請發郵件至如下郵箱

  gdg-shanghai+subscribe@googlegroups.com


本文分享自微信公衆號 - GDG(GDG_Shanghai)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索