使用kotlin的协程一段时间后,我们或多或少会产生一些疑问:协程和线程有什么关系?协程之间到底怎么来回传递的?协程真的比线程(池)好吗?

初窥

首先我们从最简单协程开始:

fun main() {

GlobalScope.launch(Dispatchers.IO) {

val aaa = async {

println("aaa-")

}

aaa.await()

println("bbb-")

}

//sleep只是防止main挂掉

Thread.sleep(100_000)

}

我们遵循“如果看不懂,那就看一下字节码或者转成java”的套路,看一下java代码大概的样子:

public static final void main() {

//协程开始

BuildersKt.launch$default((CoroutineScope) GlobalScope.INSTANCE, (CoroutineContext) Dispatchers.getIO(), (CoroutineStart)null,

//这里传了个回调(续体)

(Function2)(new Function2((Continuation)null) {

int label;

@Nullable

public final Object invokeSuspend(@NotNull Object $result) {

//很明显,这是协程刚开始执行的代码

Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();

switch(this.label) {

case 0:

//label默认是0,所以走初始化和创建aaa的回调(续体)

ResultKt.throwOnFailure($result);

CoroutineScope $this$launch = (CoroutineScope)this.L$0;

System.out.println("开始");

Deferred aaa = BuildersKt.async$default($this$launch, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {

int label;

@Nullable

public final Object invokeSuspend(@NotNull Object var1) {

Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();

switch(this.label) {

case 0:

//aaa的代码执行

ResultKt.throwOnFailure(var1);

System.out.println("aaa-");

return Unit.INSTANCE;

default:

throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");

}

}

...

}), 3, (Object)null);

//label赋值1,如果下次再调用就会走case 1了

this.label = 1;

//获取aaa的实时结果,并且把自己(回调)传了进去

if (aaa.await(this) == var5) {

//如果aaa告诉你等一会再给结果(参考var5赋值),则代码结束

return var5;

}

break;

case 1:

ResultKt.throwOnFailure($result);

break;

}

//上面相当于传给了aaa一个回调(续体),当aaa执行完成后会再次调用invokeSuspend

//在case 1里,如果aaa正常完成,则会进到这里

//最终结束

System.out.println("结束");

return Unit.INSTANCE;

}

...

}), 2, (Object)null);

Thread.sleep(100000L);

}

因为上面有label这种状态机制(状态机),不方便理解,我们画成流程图

 依据上面也能很轻松就能推理出,如果我需要await aaa、bbb、ccc三个,我们只需要在“准备执行结束代码”前面塞上和aaa一样的bbb、ccc逻辑即可,如下

 如果想深入查看了解源码请移步:Kotlin协程实现原理 - Giagor - 博客园 (cnblogs.com)

困境

看了上面的基本原理,有没有产生个疑问:它和线程池有什么区别?

我们先仿照上面用线程池实现一下:

//线程池版

fun main2() {

//ThreadPool为线程池

ThreadPool.run {

println("开始")

ThreadPool.run {

println("aaa-")

ThreadPool.run {

println("结束")

}

}

}

//sleep只是防止main挂掉

Thread.sleep(100_000)

}

 当然如果涉及到aaa、bbb、ccc都并发的情况下,我们需要重新革命一下代码逻辑

//线程池版

fun main2() {

//ThreadPool为线程池

ThreadPool.run {

println("开始")

ThreadPool.run {

println("aaa-")

callEnd()

}

ThreadPool.run {

println("bbb-")

callEnd()

}

ThreadPool.run {

println("ccc-")

callEnd()

}

}

//sleep只是防止main挂掉

Thread.sleep(100_000)

}

val callCount = AtomicInteger(0)

fun callEnd() {

val count = callCount.incrementAndGet()

//用count来计数,当三个都成功时结束

if (count == 3) {

ThreadPool.run {

println("结束")

}

}

}

虽然代码可能比协程多了一点,但它丝毫不影响我对协程的推测:协程是一个共用线程池。翻开“Dispatchers.IO”的源码——没错它就是一个公共线程池。

是不是有一种:把协程吹得那么高大尚,就这?

破局

还记得近期大火的“三体”吗,汪淼花了大量时间破解三体的太阳之谜,而最终的答案在哪里呢?没错“三体”就是三个物体因力学关系互相影响而无法归纳他们的运动轨迹。而我们协程的全称叫“协同程序”,所以作为协同程序它的目标是:

1. 轻量:协程全局共享线程,消耗资源较少

2.灵活:轻松编写非阻塞式异步代码

3.简洁:避免回调地狱,避免过多改动代码,更友好简单的代码

4.可控:启动、暂停、恢复、异常等均可自行管理控制

释然

协程和线程池目标不同,并且各有所长,只不过大部分情况下协程比较占优罢了。

线程cpu执行的基本单元,和上面两个的概念完全不同,并且多cpu架构的协程必然存在线程池。

好文阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。