Kotlin进阶-协程基础
1.什么是协程
我们先来看以下程序
开启一个协程来打印“World!”,在主线程中打印“Hello,”,主线程打印结束后阻塞2秒等待协程打印
image.png运行结果:
Hello,
World!
我们分别来看下协程和主线程的名称
image.png运行结果:
Hello,
main
World!
DefaultDispatcher-worker-1
可以看到,主线程的线程名称为main,而协程的线程名称为DefaultDispatcher-worker-1,说明Kotlin协程在运行时创建了一个Java中的子线程
那什么是协程呢?
本质上,协程就是轻量级的线程。Kotlin提供的Java的线程框架
分析上面的代码:
GlobalScope:作用域构建器,后续会详细讲解。
delay: delay 是一个特殊的挂起函数 ,它不会造成线程阻塞,但是会挂起协程,并且只能在协程中使用。
2.挂起和阻塞
挂起:一般是主动的,由系统或程序发出,阻塞当前作用域,但所在线程还在执行(执行其他协程作用域)。(不是放CPU,可能释放内存,放在外存)
阻塞:一般是被动的,在抢占资源中得不到资源,被动的挂起在内存,等待某种资源或信号量(即有了资源)将它唤醒。(释放CPU,不是放内存)
delay挂起函数的定义:public suspend fun delay(timeMillis: Long)
使用suspend关键字修饰,意思是挂起,作为用户,阻塞和挂起效果是一样的,都是停止运行。
也可以使用runBlocking来实现阻塞主线程
image.png上面代码结果是相似的,但是这些代码只使用了非阻塞的函数delay,并没有使用sleep。
调用了runBlocking的主线程会一直阻塞直到runBlocking内部的协程执行完毕。
这个示例可以使用更合乎习惯用法的方式重写,使用 runBlocking 来包装 main 函数的执行
image.png以上把主线程包装成了一个Kotlin的协程
3.等待一个作业
延迟一段时间来等待另一个协程运行并不是一个好的选择。让我们显式(以非阻塞方式)等待所启动的后台 Job 执行结束:
image.png现在,结果仍然相同,但是主协程与后台作业的持续时间没有任何关系了。好多了。
4.结构化的并发
在我们的示例中,我们使用 runBlocking 协程构建器将 main 函数转换为协程。包括 runBlocking 在内的 每个协程构建器都将 CoroutineScope 的实例添加到其代码块所在的作用域中。我们可以在这个作用域中启动 协程而无需显式 join 之,因为外部协程(示例中的 runBlocking)直到在其作用域中启动的所有协程都执 行完毕后才会结束。因此,可以将我们的示例简化为:
image.png5.作用域构建器
除了由不同的构建器提供协程作用域之外,还可以使用 coroutineScope 构建器声明自己的作用域。它会创建一 个协程作用域并且在所有已启动子协程执行完毕之前不会结束。
runBlocking 与 coroutineScope 可能看起来很类似,因为它们都会等待其协程体以及所有子协程结束。主要 区别在于,runBlocking 方法会阻塞当前线程来等待,而 coroutineScope 只是挂起,会释放底层线程用于其他 用途。由于存在这点差异,runBlocking 是常规函数,而 coroutineScope 是挂起函数。
可以通过以下示例来演示:
运行结果:
Task from coroutine scope // 自己创建的协程作用域只delay100ms,delay时间最短,所以先打印
Task from runBlocking // runBlocking自带的协程作用域delay200ms,第二个打印
Task from nested launch // 自己创建的额协程作用域中delay了500ms,第三个打印
Coroutine scope is over // 自己创建的协程作用域(soroutinesScope方法)内部都执行完才会向下执行,所以在自己创建的协程作用域执行完后才打印
6.提取函数重构
我们来将 launch { ...... } 内部的代码块提取到独立的函数中。当你对这段代码执行“提取函数”重构时,你会 得到一个带有 suspend 修饰符的新函数。这是你的第一个挂起函数。在协程内部可以像普通函数一样使用挂 起函数,不过其额外特性是,同样可以使用其他挂起函数(如本例中的 delay)来挂起协程的执行。
image.png运行结果:
Hello,
World!
7.协程很轻量
image.png它启动了 10 万个协程,并且在 5 秒钟后,每个协程都输出一个点。
现在,尝试使用线程来实现。会发生什么?(很可能你的代码会产生某种内存不足的错误)
启动10 万个协程并不会创建10 万个线程
8.全局协程像守护线程
以下代码在 GlobalScope 中启动了一个⻓期运行的协程,该协程每秒输出“I'm sleeping”两次,之后在主函数中 延迟一段时间后返回。
image.png你可以运行这个程序并看到它输出了以下三行后终止:
image.png在 GlobalScope 中启动的活动协程并不会使进程保活。它们像守护线程一样。