前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kotlin协程开篇

Kotlin协程开篇

作者头像
PhoenixZheng
发布2021-04-09 10:29:48
8730
发布2021-04-09 10:29:48
举报

《Kotlin协程》均基于Kotlinx-coroutines 1.3.70

新开个坑,专门讲kotlin的协程。聊协程之前先说一下具体聊的是协程的什么内容。

· 协程是什么?

· 什么时候用协程?

· 协程的核心是什么?

· kotlin的协程和其他语言的协程有什么异同?

kotlin的协程的出现其实比kotlin语言还晚一点。在当前这个版本,协程甚至都还处于一个不稳定的迭代版本中。协程到目前为止都还没进入kotlin的标准库,它是一个独立的依赖库,叫 Kotlinx。对于想在开发中使用协程的人来说,需要在依赖里加入kotlinx-core依赖。作为一个独立的依赖包,它的源码可以从github上获取,《Kotlin协程》分析的源码就是以github上的master分支为参考。

协程没那么难

协程的出现是为了解决异步编程中遇到的各种问题。从高级编程语言出现的第一天,异步执行的问题就伴随出现。

在Kotlin里使用协程非常方便,

代码语言:javascript
复制
import kotlinx.coroutines.*

fun main() {
    GlobalScope.launch { // 在后台启动一个新的协程并继续
        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("World!") // 在延迟后打印输出
    }
    println("Hello,") // 协程已在等待时主线程还在继续
    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}

上面的代码是一个常规启动协程的方式,关键函数只有 launch,delay,这两个函数是kotlin协程独有的。其他函数都属于基本库。

代码的输出结果是

Hello, World!

这是一个典型的异步执行结果,先得到 Hello,而不是按代码顺序先得到 World。

异步执行在平时开发中经常遇到,比如执行一段IO操作,不管是文件读写,还是网络请求,都属于IO。

在Android中我们对IO操作的一个熟知的规则是不能写在主线程中,因为它会卡线程,导致ANR。而上面的代码其实是不会卡线程的。用同步的方式写异步代码 这句话在很多资料中出现过,划重点。

理解这句话的关键在于,协程干了什么,让这个异步操作不会卡主线程?

我们知道类似的技术在RxJava中也有,它通过手动切线程的方式指定代码运行所在的线程,从而达到不卡主线程的目的。而协程的高明和简洁之处在于,开发者不需要主动切线程。

在上面的代码中打印一下线程名观察结果。

代码语言:javascript
复制
import kotlinx.coroutines.*

fun main() {
    GlobalScope.launch { // 在后台启动一个新的协程并继续
        println("Thread: ${Thread.currentThread().name}")
        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("World!") // 在延迟后打印输出
    }
    println("Thread: ${Thread.currentThread().name}")
    println("Hello,") // 协程已在等待时主线程还在继续
    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}

我们会得到

Thread: main Hello, Thread: DefaultDispatcher-worker-1 World!

可以看到在打印World的时候,代码是运行在子线程的。

协程其实没那么容易

对于经常用协程开发的人来说,有几个很有意思的问题值得思考下。· 上面代码中的Thread.sleep()可以改成delay()吗?

· 为什么理论上可以开无限多个coroutine?

· 假设有一个IO操作 foo() 耗时a,一个计算密集操作 bar() 耗时b,用协程来执行的话,launc{a b} 耗时c,c是否等于a + b?

另外一个很有意思的问题需要用代码来展示。在协程中有一个函数 runBlocking{},没接触过的可以简单理解为它等价于launch{}。

用它来改造上面的代码,

代码语言:javascript
复制
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking


fun main() = runBlocking {
    GlobalScope.launch { // 在后台启动一个新的协程并继续
        println("Thread: ${Thread.currentThread().name}")
        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("World!") // 在延迟后打印输出
    }
    println("Thread: ${Thread.currentThread().name}")
    println("Hello,") // 协程已在等待时主线程还在继续
    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}

我们会得到

Thread: DefaultDispatcher-worker-1 Thread: main Hello, World!

现在我们把 GlobalScope.launch这行改造一下,

代码语言:javascript
复制
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    launch { // 在后台启动一个新的协程并继续
        println("Thread: ${Thread.currentThread().name}")
        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("World!") // 在延迟后打印输出
    }
    println("Thread: ${Thread.currentThread().name}")
    println("Hello,") // 协程已在等待时主线程还在继续
    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}

现在再看执行结果,

Thread: main Hello, Thread: main World!

WTF? launch里的代码也执行在主线程了?

这个问题涉及到Kotlin协程的Scope,调度,也是协程的实现核心逻辑

Kotlin不是第一个提出协程的

实际上在Kotlin之前就有不少语言实践了协程这个概念。比如python,golang。

而最原始的协程其实不叫协程,叫纤程(Fiber)。听说过Fiber的人都已经。。

甲:听说过纤程吗 乙:Fiber是吧 甲:你今年起码40岁了吧

纤程是微软第一个提出的,但因为它的使用非常的反人类,对程序员的代码质量要求非常高,以至于没人愿意用它。虽然现在还可以在微软官网上找到关于纤程的资料,但能用好纤程的程序员凤毛麟角。

Using Fibers

直到golang的出现,才把协程这个技术发扬光大。有人说python也有协程呀,为什么是golang。其实python的协程不是真正意义上的协程,后面我们会说到。python的协程是基于yield关键字进行二次封装的,虽然在高层抽象上也是以函数作为协程粒度,但对比golang差的太远。

golang做了什么

golang的协程叫goroutine,跟kotlin的coroutine差不多。golang用一种程序员更容易理解的抽象定义了协程粒度goroutine,还有它的各种操作。

对于程序员来说,再也不用关心什么时候切协程,协程在什么线程运行这种问题,开发效率和代码运行效率得到成倍提升。

golang在编译器上做了很多优化,当代码中发生IO或者内核中断的时候,会自动帮你切协程。熟悉计算机原理的能明白,当发生内核中断的时候,比如请求一个磁盘文件,中断发生时CPU其实是没有工作的,执行逻辑在这个时候处于一个空转,直到中断返回结果才继续往下执行。

于是在中断发生的时候,CPU相当于浪费了一段时间。golang在这个时候切协程,就能把CPU浪费的算力利用起来交给另外一个协程去执行。

kotlin的协程还在发展

如果去看kotlin的协程源码的话会发现里面有很多 exeprimental 的api和实现逻辑。直到1.3.70为止,jetbain团队还在继续地为coroutine机制增加新的活力。目前来说coroutine处于一个稳定阶段,可以基于1.3.70版本来分析它,后面应该不会有很大机制上的变动了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-04-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android每日一讲 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 协程没那么难
  • 协程其实没那么容易
  • Kotlin不是第一个提出协程的
    • golang做了什么
      • kotlin的协程还在发展
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档