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源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索