kotlin协程原理分析

使用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这种状态机制(状态机),不方便理解,我们画成流程图

kotlin协程原理分析_第1张图片

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

kotlin协程原理分析_第2张图片

 如果想深入查看了解源码请移步: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架构的协程必然存在线程池。

你可能感兴趣的:(kotlin,android,协程)