协程是一种非抢占式(协作式)的任务调度模式,程序可以主动挂起或者恢复执行。Kotlin 协程的核心竞争力在于:它能简化异步并发任务,以同步方式写异步代码。
协程基于线程,但相对于线程轻量很多。可以理解为在用户层模拟线程操作;
线程的上下文切换涉及到用户态和内核态的切换,而协程的上下文切换完全是在用户态控制的,避免了大量的中断参与,减少了线程上下文切换与调度的资源消耗
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换。
Kotlin
在语言级别并没有实现一种同步机制(锁),还是依靠Kotlin-JVM
的提供的Java
关键字(如synchronized
),即锁的实现还是交给线程处理
因而Kotlin
协程本质上只是一套基于原生Java线程池
的封装。
Kotlin
协程的核心竞争力在于:它能简化异步并发任务,以同步方式写异步代码。
下面介绍一些kotin
协程中的基本概念
使用suspend关键字修饰的函数叫做挂起函数,挂起函数只能在协程内或其他挂起函数内使用。
协程内部挂起函数的调用处被称为挂起点,挂起点如果出现异步调用就会挂起当前协程,知道对应的continuation的resume函数被调用才会恢复执行。
suspend
的本质,就是 CallBack
。
suspend fun getUserInfo(): String {
withContext(Dispatchers.IO) {
delay(1000L)
}
return "BoyCoder"
}
将挂起函数的字节码反编译成java代码简化后得到如下结果:
public final Object getUserInfo(@NotNull Continuation var1) {
return "BoyCoder";
}
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}
可以看到,编译器会给挂起函数添加一个Continuation
参数,这被称为CPS 转换(Continuation-Passing-Style Transformation)
suspend
函数不能在协程体外调用的原因也可以知道了,就是因为这个Continuation
实例的传递。
动画演示挂起函数CPS转换过程:
- 增加了
Continuation
类型的参数 - 返回类型从
String
转变成了Any
CPS
转换,其实就是将原本的同步挂起函数
转换成CallBack
异步代码的过程。
这个转换是编译器在背后做的,我们对此无感知。