通过线程一窥协程
我们先来看一张协程与线程对比图:图片来源
- 协程本质上可以认为是运行在线程上的代码块,协程提供的 挂起 操作会使协程暂停执行,而不会导致线程阻塞。
- 协程是一种轻量级资源,如上图所示,即使创建了上千个协程,对于系统来说也不是一种很大的负担。
- 包含关系上看,协程跟线程的关系,有点像“线程与进程的关系”,毕竟,协程不可能脱离线程运行;协程虽然不能脱离线程而运行,但可以在不同的线程之间切换,如上图所示。
- 协程的核心竞争力在于:简化异步并发任务(同步的写法实现异步操作)。
一窥协程之后,我们再来一步步深入,深入之前先了解一些前置知识,来更好的帮助我们理解。
前置知识
Continuation Passing Style(CPS)
Continuation Passing Style翻译过来就是“续体传递风格”,后面就简称CPS。简单点说CPS其实就是函数通过回调传递结果,先让我们看看例子:
//常规写法
class Exemple {
public static long sum(int a, int b) {
return a + b;
}
public static void main(String[] args) {
System.out.println(sum(1, 2));
}
}
//CPS风格
class Exemple {
interface Continuation {
void next(int result);
}
public static void sum(int a, int b, Continuation continuation) {
continuation.next(a+ b);
}
public static void main(String[] args) {
plus(1, 2, result -> System.out.println(result));
}
}
很简单吧,这就是CPS风格,函数的结果通过回调来传递。协程里通过, 协程里通过在CPS的Continuation回调里结合状态机流转,来实现协程挂起-恢复的功能。
协程中的续体——Continuation
Kotlin中被suspend修饰符修饰的函数在编译期间会被编译器做特殊处理。而首先处理的就是CPS变换,他会改变挂起函数的函数签名。
例如下面这个suspend函数:
suspend fun getResponse(): Response {
val response = doRequest() //doRequest() 模拟网络请求
return response
}
在编译期发生CPS转换后,反编译成java,结果会是这样:
public static final Object getResponse(Continuation $completion) {
...
}
我们可以看到编译器对函数签名做了改变,这种改变就是上节中说过的CPS(续体传递风格)变换。
发生了CPS变换后的函数多了一个Continuation(续体)类型的参数,它的声明如下:
interface Continuation<in T> {
val context: CoroutineContext
fun resumeWith(result: Result<T>)//result 为返回的结果
}
- 续体是一个较为抽象的概念,简单来说它包装了协程在挂起之后应该继续执行的代码;在编译的过程中,一个完整的协程被分割切块成一个又一个续体。
- 在suspend函数或者 await 函数的挂起结束以后,它会调用 continuation 参数的 resumeWith 函数,来恢复执行suspend函数或者await 函数后面的代码。
下面就看下suspend函数CPS转换后的伪代码:
fun getResponse(ct:Continuation):Any?{
val response = doRequest()
ct.resumeWith(response)
}
注:可以将Continuation认为是一个Callback,这样是不是更好理解了。
最后还要提一点,我们看到发生 CPS 变换的函数,返回值类型变成了 Any?,这是因为这个函数在发生变换后,除了要返回它本身的返回值,还要返回一个标记CoroutineSingletons.COROUTINE_SUSPENDED,为了适配各种可能性,CPS 转换后的函数返回值类型就只能是 Any?
了。至于CoroutineSingletons.COROUTINE_SUSPENDED是什么,后面我们会说到。
协程的启动
例子:
object CoroutineExample {
private val TAG: String = "CoroutineExample"
fun main(){
GlobalScope.launch(Main) {
request()
}
}
private suspend fun request(): String {
delay(2000)
Log.e(TAG, "request complete")
return "result from request"
}
}
GlobalScope.launch()
从 CoroutineScope.launch
开始跟踪协程启动流程:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy){
LazyStandaloneCoroutine(newContext, block)
}else{
StandaloneCoroutine(newContext, active = true)
}
coroutine.start(start, coroutine, block)
return coroutine
}
- 参数一context:协程上下文,并不是我们平时理解的Android中的上下文,它是一种key-value数据结构。此处我们传入了Main用于主线程调度,这部分这里就先不扩展了。
- 参数二start:启动模式,此处我们没有传值则为默认值(DEFAULT),共有三种启动模式。
- DEFAULT:默认模式,创建即启动协程,可随时取消;
- ATOMIC:自动模式,创建即启动协程,启动前不可取消;
- LAZY:延迟启动模式,只有当调用start方法时才能启动。
- 参数三block:协程真正执行的代码块,即上面例子中launch{}闭包内的代码块。
SuspendLambda
CoroutineScope.launch
中第三个参数类型为suspend CoroutineScope.() -> Unit
函数,这是怎么来的呢?我们编写代码的时候并没有这个东西,其实它由编译器生成的,我们的block
代码块经过编译器编译后会生成一个继承Continuation
的类SuspendLambda。一起看下反编译的java代码,为了关注主要逻辑方便理解,去掉了一些无关代码大概代码如下:
public final void main() {
BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)Dispatchers.getMain(), (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
ResultKt.throwOnFailure($result);
CoroutineExample var10000 = CoroutineExample.this;
this.label = 1;
if (var10000.request(this) == var2) {
return var2;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return Unit.INSTANCE;
}
···
}
从上面反编译的java代码中好像并不能很好的看出来协程中的block代码块具体编译长什么样子,但可以确定他是编译成了Continuation
类,因为我们可以看到实现的invokeSuspend
方法实际是来自BaseContinuationImpl
,而BaseContinuationImpl
的父类就是Continuation
。这个继承关系我们后面再说。既然从反编译的java代码中看的不明显,我们直接看上面例子的字节码文件,其中可以很明显的看到这样一段代码:
final class com/imile/pda/CoroutineExample$main$1 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function2
这下恍然大悟,launch函数的第三个参数,即协程中的block代码块是一个编译后继承了SuspendLambda
并且实现了Function2
的实例。SuspendLambda
本质上是一个 Continuation
,前面我们已经说过 Continuation 是一个有着恢复操作的接口,其 resume 方法可以恢复协程的执行。
SuspendLambda
继承机构如下:
- Continuation: 续体,恢复协程的执行
- BaseContinuationImpl: 实现 resumeWith(Result) 方法,控制状态机的执行,定义了 invokeSuspend 抽象方法
- ContinuationImpl: 增加 intercepted 拦截器,实现线程调度等
- SuspendLambda: 封装协程体代码块
- 协程体代码块生成的子类: 实现 invokeSuspend 方法,其内实现状态机流转逻辑
每一层封装都对应添加了不同的功能,我们先忽略掉这些功能细节,着眼于我们的主线,继续跟进launch
函数执行过程,由于第二个参数是默认值(DEFAULT),所以创建的是 StandaloneCoroutine
, 最后启动协程:
// 启动协程
coroutine.start(start, coroutine, block)
// 启动协程
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
start(block, receiver, this)
}
上面 coroutine.start
的调用涉及到运算符重载,实际上会调到 CoroutineStart.invoke()
方法:
public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
when (this) {
DEFAULT -> block.startCoroutineCancellable(receiver, completion)
ATOMIC -> block.startCoroutine(receiver, completion)
UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
LAZY -> Unit // will start lazily
}
这里启动方式为DEFAULT,所以接着往下看:
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
receiver: R, completion: Continuation<T>,
onCancellation: ((cause: Throwable) -> Unit)? = null
) = runSafely(completion) {
createCoroutineUnintercepted(receiver, completion)
.intercepted()
.resumeCancellableWith(Result.success(Unit), onCancellation)
}
整理下调用链如下:
coroutine.start(start, coroutine, block)
-> CoroutineStart.start(block, receiver, this)
-> CoroutineStart.invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>)
-> block.startCoroutineCancellable(receiver, completion)
->
createCoroutineUnintercepted(receiver,completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
最后走到createCoroutineUnintercepted(receiver,completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
,这里创建了一个协程,并链式调用 intercepted
、resumeCancellable
方法,利用协程上下文中的 ContinuationInterceptor
对协程的执行进行拦截,intercepted
实际上调用的是 ContinuationImpl
的 intercepted
方法:
internal abstract class ContinuationImpl(
completion: Continuation<Any?>?,
private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
...
public fun intercepted(): Continuation<Any?> =
intercepted
?:(context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
...
}
context[ContinuationInterceptor]?.interceptContinuation
调用的是 CoroutineDispatcher
的 interceptContinuation
方法:
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
内部创建了一个 DispatchedContinuation
可分发的协程实例,我们继续进到看resumeCancellableWith
方法:
internal class DispatchedContinuation<in T>(
@JvmField val dispatcher: CoroutineDispatcher,
@JvmField val continuation: Continuation<T>
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {
...
public fun <T> Continuation<T>.resumeCancellableWith(
result: Result<T>,
onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
// 判断是否是DispatchedContinuation 根据我们前面的代码追踪 这里是DispatchedContinuation
is DispatchedContinuation -> resumeCancellableWith(result, onCancellation)
else -> resumeWith(result)
}
inline fun resumeCancellableWith(
result: Result<T>,
noinline onCancellation: ((cause: Throwable) -> Unit)?
) {
val state = result.toState(onCancellation)
// 判断是否需要线程调度
// 由于我们之前使用的是 `GlobalScope.launch(Main)` Android主线程调度器所以这里为true
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_CANCELLABLE
dispatcher.dispatch(context, this)
} else {
executeUnconfined(state, MODE_CANCELLABLE) {
if (!resumeCancelled(state)) {
resumeUndispatchedWith(result)
}
}
}
}
...
}
最终走到 dispatcher.dispatch(context, this)
而这里的 dispatcher
就是通过工厂方法创建的 HandlerDispatcher
,dispatch()
函数第二个参数this
是一个runnable
这里为 DispatchedTask
HandlerDispatcher
internal class HandlerContext private constructor(
private val handler: Handler,
private val name: String?,
private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
...
// 最终执行这里的 dispatch方法 而handler则是android中的 MainHandler
override fun dispatch(context: CoroutineContext, block: Runnable) {
if (!handler.post(block)) {
cancelOnRejection(context, block)
}
}
...
}
这里借用 Android 的主线程消息队列来在主线程中执行 block Runnable
而这个 Runnable
即为 DispatchedTask
:
internal abstract class DispatchedTask<in T>(
@JvmField public var resumeMode: Int
) : SchedulerTask() {
...
public final override fun run() {
...
withContinuationContext(continuation, delegate.countOrElement) {
...
if (job != null && !job.isActive) {
val cause = job.getCancellationException()
cancelCompletedResult(state, cause)
// 异常情况下
continuation.resumeWithStackTrace(cause)
} else {
if (exception != null) {
// 异常情况下
continuation.resumeWithException(exception)
} else {
// step1:正常情况下走到这一步
continuation.resume(getSuccessfulResult(state))
}
}
}
...
}
}
//step2:这是Continuation的扩展函数,内部调用了resumeWith()
@InlineOnly public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
//step3:最终会调用到BaseContinuationImpl的resumeWith()方法中
internal abstract class BaseContinuationImpl(...) {
// 实现 Continuation 的 resumeWith,并且是 final 的,不可被重写
public final override fun resumeWith(result: Result<Any?>) {
...
val outcome = invokeSuspend(param)
...
}
// 由编译生成的协程相关类来实现,例如 CoroutineExample$main$1
protected abstract fun invokeSuspend(result: Result<Any?>): Any?
}
最终调用到 continuation.resumeWith()
而 resumeWith()
中会调用 invokeSuspend
,即之前编译器生成的 SuspendLambda
中的 invokeSuspend
方法:
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
ResultKt.throwOnFailure($result);
CoroutineExample var10000 = CoroutineExample.this;
this.label = 1;
if (var10000.request(this) == var2) {
return var2;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
}
}
这段代码是一个状态机机制,每一个挂起点都是一种状态,协程恢复只是跳转到下一个状态,挂起点将执行过程分割成多个片段,利用状态机的机制保证各个片段按顺序执行。
可以看到协程非阻塞的异步底层实现其实就是一种Callback回调(这一点我们在介绍Continuation时有提到过),只不过有多个挂起点时就会有多个Callback回调,这里协程把多个Callback回调封装成了一个状态机。
以上就是协程的启动过程,下面我们再来看下协程中的重点挂起和恢复。
协程的挂起与恢复
协程的启动,挂起和恢复有两个关键方法: invokeSuspend()
和 resumeWith(Result)
。我们以上一节中的例子,反编译后逆向剖析协程的挂起和恢复,先整体看下是怎样的一个过程。
suspend fun reqeust(): String {
delay(2000)
return "result from request"
}
反编译后的代码如下(为了方便理解,代码有删减和修改):
//1.函数返回值由String变成Object,入参也增加了Continuation参数
public final Object reqeust(@NotNull Continuation completion) {
//2.通过completion创建一个ContinuationImpl,并且复写了invokeSuspend()
Object continuation;
if (completion instanceof <undefinedtype>){
continuation = <undefinedtype>completion
}else{
continuation = new ContinuationImpl(completion) {
Object result;
int label; //初始值为0
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return request(this);//又调用了requestUserInfo()方法
}
};
}
Object $result = (continuation).result;
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
//状态机
//3.方法被恢复的时候又会走到这里,第一次进入case 0分支,label的值从0变为1,第二次进入就会走case 1分支
switch(continuation.label) {
case 0:
ResultKt.throwOnFailure($result);
continuation.label = 1;
//4.delay()方法被suspend修饰,传入一个continuation回调,返回一个object结果。这个结果要么是`COROUTINE_SUSPENDED`,否则就是真实结果。
Object delay = DelayKt.delay(2000L, continuation)
if (delay == var4) {//如果是COROUTINE_SUSPENDED则直接return,就不会往下执行了,requestUserInfo()被暂停了。
return var4;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return "result from request";
}
挂起过程:
- 函数返回值由
String
变成Object
,编译器自动增加了Continuation
参数,相当于帮我们添加Callback。 - 根据
completion
创建了一个ContinuationImpl
(如果已经创建就直接用,避免重复创建),复写了invokeSuspend()
方法,在这个方法里面它又调用了request()
方法,这里又调用了一次自己(是不是很神奇),并且把continuation
传递进去。 - 在 switch 语句中,
label
的默认初始值为 0,第一次会进入case 0
分支,delay()
是一个挂起函数,传入上面的continuation
参数,会有一个Object
类型的返回值。这个结果要么是COROUTINE_SUSPENDED
,否则就是真实结果。(关于delay是如何返回COROUTINE_SUSPENDED,可自行跟下源码,这里就不展开) -
DelayKt.delay(2000, continuation)
的返回结果如果是COROUTINE_SUSPENDED
, 则直接 return ,那么方法执行就被结束了,方法就被挂起了。
- 函数即便被
suspend
修饰了,但是也未必会挂起。需要里面的代码编译后有返回值为COROUTINE_SUSPENDED
这样的标记位才可以。- 协程的挂起实际是方法的挂起,本质是return。
恢复过程:
因为
delay()
是 IO操作,在2000ms后就会通过传递给它的continuation
回调回来。回调到
ContinuationImpl
类的resumeWith()
方法,会再次调用invokeSuspend()
方法,进而再次调用requestUserInfo()
方法。程序会再次进入switch语句,由于第一次在
case 0
时把label = 1
赋值为1,所以这次会进入case 1
分支,并且返回了结果result from request
。并且
request()
的返回值作为invokeSuspend()
的返回值返回。重新被执行的时候就代表着方法被恢复了。
看到大家一定会疑问,步骤2中invokeSuspend()
是如何被再次调用呢?我们都知道 ContinuationImpl
的父类是 BaseContinuationImpl
,实际上ContinuationImpl
中调用的resumeWith()
是来自父类BaseContinuationImpl
。
internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
//这个实现是最终的,用于展开 resumeWith 递归。
public final override fun resumeWith(result: Result<Any?>) {
var current = this
var param = result
while (true) {
with(current) {
val completion = completion!!
val outcome: Result<Any?> =
try {
// 1.调用 invokeSuspend()方法执行,执行协程的真正运算逻辑,拿到返回值
val outcome = invokeSuspend(param)
// 2.如果返回的还是COROUTINE_SUSPENDED则提前结束
if (outcome == COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
if (completion is BaseContinuationImpl) {
//3.如果 completion 是 BaseContinuationImpl,内部还有suspend方法,则会进入循环递归,继续执行和恢复
current = completion
param = outcome
} else {
//4.否则是最顶层的completion,则会调用resumeWith恢复上一层并且return
// 这里实际调用的是其父类 AbstractCoroutine 的 resumeWith 方法
completion.resumeWith(outcome)
return
}
}
}
}
实际上任何一个挂起函数它在恢复的时候都会调到 BaseContinuationImpl
的 resumeWith()
方法里面。
- 一但
invokeSuspend()
方法被执行,那么request()
又会再次被调用,invokeSuspend()
就会拿到request()
的返回值,在ContinuationImpl
里面根据val outcome = invokeSuspend()
的返回值来判断我们的request()
方法恢复了之后的操作。 - 如果
outcome
是COROUTINE_SUSPENDED
常量(可能挂起函数中又返回了一个挂起函数),说明你即使被恢复了,执行了一下,if (outcome == COROUTINE_SUSPENDED) return
但是立马又被挂起了,所以又 return 了。 - 如果本次恢复
outcome
是一个正常的结果,就会走到completion.resumeWith(outcome)
,当前被挂起的方法已经被执行完了,实际调用的是其父类AbstractCoroutine
的resumeWith
方法,那么协程就恢复了。
我们知道
request()
肯定是会被协程调用的(从上面反编译代码知道会传递一个Continuation completion
参数),request()
方法恢复完了就会让协程completion.resumeWith()
去恢复,所以说协程的恢复是方法的恢复,本质其实是callback(resumeWith)回调。
一张图总结一下:
协程的核心是挂起——恢复,挂起——恢复的本质是return & callback
回调
协程挂起
我们说过协程启动后会调用到上面这个 resumeWith() 方法,接着调用其 invokeSuspend() 方法:
- 当 invokeSuspend() 返回 COROUTINE_SUSPENDED 后,就直接 return 终止执行了,此时协程被挂起。
- 当 invokeSuspend() 返回非 COROUTINE_SUSPENDED 后,说明协程体执行完毕了,对于 launch 启动的协程体,传入的 completion 是 AbstractCoroutine 子类对象,最终会调用其 AbstractCoroutine.resumeWith() 方法做一些状态改变之类的收尾逻辑。至此协程便执行完毕了。
协程恢复
这里我们接着看上面第一条:协程执行到挂起函数被挂起后,当这个挂起函数执行完毕后是怎么恢复协程的,以下面挂起函数为例:
private suspend fun login() = withContext(Dispatchers.IO) {
Thread.sleep(2000)
return@withContext true
}
通过反编译可以看到上面挂起函数中的函数体也被编译成了 SuspendLambda 的子类,创建其实例时也需要传入 Continuation 续体参数(调用该挂起函数的协程所在续体)。贴下 withContext 的源码:
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T {
return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
// 创建 new context
val oldContext = uCont.context
val newContext = oldContext + context
// 检查新上下文是否作废
newContext.ensureActive()
// 新上下文与旧上下文相同
if (newContext === oldContext) {
val coroutine = ScopeCoroutine(newContext, uCont)
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
// 新调度程序与旧调度程序相同
if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) {
val coroutine = UndispatchedCoroutine(newContext, uCont)
// 上下文有变化,所以这个线程需要更新
withCoroutineContext(newContext, null) {
return@sc coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
// 使用新的调度程序
val coroutine = DispatchedCoroutine(newContext, uCont)
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()
}
}
首先调用了 suspendCoroutineUninterceptedOrReturn 方法,看注释知道可以通过它来获取到当前的续体对象 uCont, 接着有几条分支调用,但最终都是会通过续体对象来创建挂起函数体对应的 SuspendLambda 对象,并执行其 invokeSuspend() 方法,在其执行完毕后调用 uCont.resume() 来恢复协程,具体逻辑大家感兴趣可以自己跟代码,与前面大同小异。
至于其他的顶层挂起函数如 await()
, suspendCoroutine()
, suspendCancellableCoroutine()
等,其内部也是通过 suspendCoroutineUninterceptedOrReturn() 来获取到当前的续体对象,以便在挂起函数体执行完毕后,能通过这个续体对象恢复协程执行。
附录
附录线程和协程间关系总结(总结来源),加深理解。
线程:
- 线程是操作系统级别的概念
- 我们开发者通过编程语言(Thread.java)创建的线程,本质还是操作系统内核线程的映射
- JVM 中的线程与内核线程的存在映射关系,有“一对一”,“一对多”,“M对N”。JVM 在不同操作系统中的具体实现会有差别,“一对一”是主流
- 一般情况下,我们说的线程,都是内核线程,线程之间的切换,调度,都由操作系统负责
- 线程也会消耗操作系统资源,但比进程轻量得多
- 线程,是抢占式的,它们之间能共享内存资源,进程不行
- 线程共享资源导致了多线程同步问题
- 有的编程语言会自己实现一套线程库,从而能在一个内核线程中实现多线程效果,早期 JVM 的“绿色线程” 就是这么做的,这种线程被称为“用户线程”
协程:
- Kotlin 协程,不是操作系统级别的概念,无需操作系统支持
- Kotlin 协程,有点像上面提到的“绿色线程”,一个线程上可以运行成千上万个协程
- Kotlin 协程,是用户态的(userlevel),内核对协程无感知
- Kotlin 协程,是协作式的,由开发者管理,不需要操作系统进行调度和切换,也没有抢占式的消耗,因此它更加高效
- Kotlin 协程,它底层基于状态机实现,多协程之间共用一个实例,资源开销极小,因此它更加轻量
- Kotlin 协程,本质还是运行于线程之上,它通过协程调度器,可以运行到不同的线程上