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

 依据上面也能很轻松就能推理出,如果我需要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架构的协程必然存在线程池。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/6540.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

XXX客户挖矿应急响应

此次为真实客户案例! 0x01 前言: 同事反馈,有客户设备告警连接挖矿域名。 0x02 事件分析: 登录设备TOP: CPU巨高!!查。 定位进程: 通过netstat -anop 查看可疑进程: 通过netstat -anop | grep 28564 查看对应进程:

初始Vue【Vue】

Vue 简介 1. Vue是什么? 一套用于构建用户界面的渐进式JavaScript框架 渐进式:Vue可以自底向上逐层的应用 简单应用:只需一个轻量小巧的核心库 复杂应用:可以引入各式各样的Vue插件 2. Vue 的特点 采用组件化的模式&#xff0c…

JVM七大垃圾回收器上篇Serial、ParNeW、Parallel Scavenge、 Serial Old、 Parallel Old、 CMS、 G1

GC逻辑分类 垃圾收集器没有在规范中进行过多的规定,可以由不同的厂商、不同版本的JVM来实现。 由于JDK的版本处于高速迭代过程中,因此Java发展至今已经衍生了众多的GC版本。 从不同角度分析垃圾收集器,可以将GC分为不同的类型。 按线程数…

Linux学习记录——십칠 基础IO(2)

文章目录理解文件系统1、了解磁盘物理结构2、逻辑抽象3、文件系统如何理解文件的增删查改4、软硬连接1、软链接2、硬链接3、继续深入理解文件系统 之前已经知道,文件在没被打开的时候,会存放在磁盘等外设中,这篇的重点就是这些没被打开的文件…

JSON 数据解析的方法

JSON 数据解析 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于ECMAScript的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C、C、C#、Java、JavaScript、Perl、Python等)。这…

百度文心一言与Notion的比较(机器人通信的例子)

文心一言出来有一段时间了,也经常会去问问,感觉对于简单的语义理解还是可以,其答案对于一些常见的常识等还是可以给出不错的答案,但是在数学与代码等方面基本上很差,基本的贷款利率、微积分、没有理解语义的代码等都是…

CSDN——Markdown编辑器——快捷键

CSDN——Markdown编辑器——快捷键CSDN——Markdown编辑器——快捷键如下4个,同 word下面的需要加 Shift标题 Ctrl /⌘Shift H // headlineF 和 G在键盘相邻快捷键 的 图标![在这里插入图片描述](https://img-blog.csdnimg.cn/dc02416cebd34c428de54aba5e66c40a.png…

蓝桥杯 --- 数学与简单DP

蓝桥杯 --- 数学与简单DP数学1205. 买不到的数目1211. 蚂蚁感冒1216. 饮料换购简单DP2. 01背包问题1015. 摘花生895. 最长上升子序列数学 1205. 买不到的数目 小明开了一家糖果店。 他别出心裁:把水果糖包成4颗一包和7颗一包的两种。 糖果不能拆包卖。 小朋友来…

springcloud:xxl-job的任务触发机制及调度过期策略

0.引言 我们都会用xxl-job,但很少有人能够说清楚xxl-job的任务触发机制,面临任务阻塞、服务重启如何处理任务,本期我们就来一起看看xxl-job的任务触发机制 1. 调度过期策略 我们在配置策略时可以看到有一个调度过期策略配置,也…

二叉树的四种遍历方式以及必备的面试题(附图解)

二叉树的四种遍历方式以及必备的面试题 文章目录二叉树的四种遍历方式以及必备的面试题前言一、构建一个二叉树二、四种遍历方式1.前序遍历2.中序遍历3.后序遍历附加: 前三种遍历对比图4.层序遍历三、四种遍历相关的面试题1.第一题:144. 二叉树的前序遍历(1)题目&am…

LeetCode.每日一题 831. 隐藏个人信息

Halo,这里是Ppeua。平时主要更新C语言,C,数据结构算法......感兴趣就关注我吧!你定不会失望。 🌈个人主页:主页链接 🌈算法专栏:专栏链接 我会一直往里填充内容哒! &…

C语言函数:内存函数memcmp()

wpC语言函数&#xff1a;内存函数memcmp()以及函数实现与使用。 memcmp()&#xff1a; 头文件:#include <string.h> 作用&#xff1a; 进行内存比较。 参数&#xff1a; 解释&#xff1a;ptr1和ptr2是指针&#xff0c;从这个两个指针开始往后num个字节&#xff0c;将两…

【Python入门第四十六天】Python丨NumPy 数组重塑

数组重塑 重塑意味着更改数组的形状。 数组的形状是每个维中元素的数量。 通过重塑&#xff0c;我们可以添加或删除维度或更改每个维度中的元素数量。 从 1-D 重塑为 2-D 实例 将以下具有 12 个元素的 1-D 数组转换为 2-D 数组。 最外面的维度将有 4 个数组&#xff0c;…

【Redis】BigKey问题

文章目录MoreKey案例大批量往redis里面插入100W测试数据key(管道)生产上限制keys*/flushdb/flushall等危险命令以防止误删误用scan命令代替了keys *,避免了查询卡顿BigKey案例多大算大key危害如何产生如何发现 redis-cli --bigkeys、memory usage如何删除->渐进式删除BigKey…

C++面经总结4

说一下new和malloc的区别 new是操作符&#xff0c; malloc是函数 malloc申请的空间是不能初始化的&#xff0c; 而new是可以初始化的 malloc申请空间的时候需要手动计算空间大小&#xff0c;而new可以直接在[]里面给个数就行。 malloc的返回值是void*, 使用时必须强转&#xf…

FFmpeg添加字幕的详细操作

FFmpeg添加字幕的详细操作 在视频中添加字幕可以使视频更具可读性&#xff0c;并为观众提供更好的观看体验&#xff0c;这在多语种内容中尤为重要。FFmpeg是一个流行的开源视频处理工具&#xff0c;它可以被用来给视频添加字幕。本文将介绍FFmpeg集成libass的编译流程&#xf…

【沐风老师】教你在3dMax中使用Greeble插件结合变形修改器建模

3dMax在Greeble中使用变形修改器 Greeble一个有趣的修改器插件,用于快速生成诸如低模城市建筑群、太空船模型、死亡星等的随机细节。。。 我们在之前的教程中介绿过Greeble的安装和基本使用方法,在本教程中,我们将学习如何使用Greeble插件和变形修改器来制作效果。 【开始…

深度学习数据集—水果数据集大合集

近期整理的各类水果&#xff08;包括干果&#xff09;数据集&#xff0c;分享给大家。 1、8类水果图片数据集&#xff08;每类100张图片左右&#xff09;[橘子&#xff0c;菠萝&#xff0c;苹果&#xff0c;木瓜&#xff0c;火龙果&#xff0c;香蕉&#xff0c;樱桃&#xff0…

聊天Chat

前言 加油 原文 聊天常用会话 ❶ Don’t count on him. 别指望他。 ❷ They underestimated the enemy’s strength. 他们低估了敌人的力量。 ❸ The plan went according to his perspective. 计划是按照他的想法进行的。 ❹ This project involves many difficulties. …

【C++】开散列哈希表封装实现unordered_map和unordered_set

在未达成目的之前&#xff0c;一切具有诱惑力的事物都显得那么不堪一击 文章目录一、unordered系列关联式容器二、哈希函数和哈希冲突三、闭散列&#xff08;你抢我的位置&#xff0c;我抢他的位置&#xff09;1.哈希表结构2.Insert()3.Erase()&#xff08;标记的伪删除法&…