kotlin协程实现原理

2023-05-16

传统runnable接口实现

在java中,很多耗时的行为通过实现runnable接口,并且通过线程运行下这些耗时的任务,例如:

public class Task1 implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println("this is task1");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这里用线程沉睡两秒的方式模拟堵塞的过程。但是这样做就带来了一个问题,那就是如果有多个耗时的任务都实现的runnable接口,而且这几个任务之间的状态相互依赖。也就说下一个runnable要依赖上一runable的变量,这种情况也算是很常见。举个简单的例子来说:

public class Task1 implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            String ret = "ok";
            System.out.println("this is task1");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


public class Task2 implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println("this is task2 ret:");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在Task1里面runalbe里面有个ret变量,如果想在Task2里面引用这个这个变量,这个问题该怎么解决?

传统解决方案

对于上述问题有很多解决方案,比如多线程之间的同步,生产者消费者模式等等,但我个人认为比较好的方案是回调和Rxjava两种模式。因为在java端的多线程调度,难免会有性能上面的影响。特别是面对成百上千的任务的相互调度。

  1. 回调
    传统回调的做法是将实现一个接口,将Runnable后加入一个回调方法,就像下面这样:
class Task1 implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            String ret = "ok";
            System.out.println("this is task1");
            new Task2().doTest(ret);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


class Task2 implements Runnable,Task{
    String ret;
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println("this is task2");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void doTest(String ret) {
        this.ret = ret;
        run();
    }
}

public interface Task {
    void doTest(String ret);
}

通过这样的方案实现了变量的传递,但是问题也出现了,回调在面对多任务的时候容易出现回调地狱的情况。

  1. RxJava
    这是一种响应式编程的典范,把runnable里面的任务看成是一个事件,一个事件完成后通知下个事件开始。
        Single.fromObservable(observer -> {
            new Task1().run();
        }).map(mapper -> {
            new Task2().doTest(mapper);
        }).subscribe(onSuccess -> {
            // do something
        },error -> {
            
        });

尽管RxJava已经充分的解耦,而且也相对比较简洁。但kotlin的协程可以说是解决这里这类问题的更优解

kotlin协程

通过前文的阐述kotlin的协程是解决任务上下相互关联的这一类问题,比如说上面的问题用协程来办,

 GlobalScope.launch { 
        val ret = Task1().doTask()
        Task2().doTask(ret)
    }

这样的写法清晰,同时将异步转成同步的思想在里面。能够如此简洁明了的解决问题,对此我想一探其底层到底是如何实现的。在官方文档说,协程可以说是轻量级的线程,从某种角度上来说,确实是如此的,因为它可以将函数挂起,不堵塞线程,而且可以唤醒函数。在这些角度上来看确实如此。但实际上的情况与线程的实现完全不一样。为了探究协程到底是什么,不妨看看它的实现。

kotlin 协程实现

我们知道java里面的线程与操作系统里面线程是一对一的关系,但协程并非这样,分析协程协程底层源码,一般通过 GlobalScope.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
}	

这里的三个参数为.上下文,线程调度器,以及我们写的回调函数,在点击start函数,通过调试其调用其默认会走到

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
    runSafely(completion) {
        createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit))
    }

请注意这行代码

createCoroutineUnintercepted(receiver,completion).intercepted().resumeCancellableWith(Result.success(Unit))

这个方法表明,首先先创建一个协程拦截器,通过intercepted方法对其方法(协程)进行挂起,在通过resumeCancellableWith方法进行启动,发现resumeCancellableWith核心实现,其代码如下:

    inline fun resumeCancellableWith(result: Result<T>) {
        val state = result.toState()
        if (dispatcher.isDispatchNeeded(context)) {
            _state = state
            resumeMode = MODE_CANCELLABLE
            dispatcher.dispatch(context, this)
        } else {
            executeUnconfined(state, MODE_CANCELLABLE) {
                if (!resumeCancelled()) {
                    resumeUndispatchedWith(result)
                }
            }
        }
    }

这个方法表明最终将执行dispatcher.dispatch(context, this),最后协程的调度将交给线程来完成。这就为什么协程在执行的时候不会堵塞当前线程的原因,因为其简单来说,就是在线程池里面取了一个线程里面执行堵塞函数。到这里我们应该知道协程到底是什么了, 用何老师的话来说,协程就是可以一起协作的例程,在kotlin中凡被suspend函数修饰的方法均可被称为协程。suspend 关键字到底做了些什么,kotlin源码

object Task {
    suspend fun deal(){
        Thread.sleep(500L)
        println("sleep 500ms")
    }
}
fun main() {
   GlobalScope.launch {
        Task.deal()
   }
   Thread.sleep(2000L)
}

通过jd-gui对TaskKt.class进行反编译成java,其suspend函数变成了

  @Nullable
  public final Object deal(@NotNull Continuation $completion) {
    Thread.sleep(500L);
    String str = "sleep 500ms";
    boolean bool = false;
    System.out.println(str);
    return Unit.INSTANCE;
  }

没有错,通过suspend修饰的函数,在编译过程会被注入一个Continuation类型的参数,打开Continuation的定义

public interface Continuation<in T> {
    /**
     * The context of the coroutine that corresponds to this continuation.
     */
    public val context: CoroutineContext

    /**
     * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
     * return value of the last suspension point.
     */
    public fun resumeWith(result: Result<T>)
}

这是个接口,context执行的上下文环境,resumeWith函数的行为则是继续执行当前这个函数,并将函数的结果返回。
而main函数在反编译的时候成了:

 @Nullable
  public final Object invokeSuspend(@NotNull Object $result) {
    Object object = IntrinsicsKt.getCOROUTINE_SUSPENDED();
    switch (this.label) {
      case 0:
        ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);
        this.label = 1;
        if (Task.INSTANCE.deal((Continuation<? super Unit>)this) == object)
          return object; 
        Task.INSTANCE.deal((Continuation<? super Unit>)this);
        return Unit.INSTANCE;
      case 1:
        ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);
        return Unit.INSTANCE;
    } 
    throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
  }
  @NotNull
  public final Continuation<Unit> create(@Nullable Object value, @NotNull Continuation<? super MainKt$main$1> $completion) {
    return (Continuation<Unit>)new MainKt$main$1($completion);
  }
  
  @Nullable
  public final Object invoke(@NotNull CoroutineScope p1, @Nullable Continuation<?> p2) {
    return ((MainKt$main$1)create(p1, p2)).invokeSuspend(Unit.INSTANCE);
  }

这里比较有意思的是通过,lable的状态来标志协程有没有结束。改变lable状态,switch来进入被挂起的函数。

总结

通过以上的分析,协程的出现主要是用来解决耗时任务相互依赖的问题,如果多个耗时任务是平级的,而且没有关联,最好采用多线程的方式并发处理。同时协程尽管在一定程度上面和线程很相似,但在底层的调度上面还是采用的线程的方式进行调度。因此你可以开大量的GlobalScope.launch,因为这本质上就是线程的调度,而且肯定并且不会堵塞,同时在同一个coroutineContext里面的函数实际上是在同一个线程里面的,这就表明了协程为什么可以解决任务间相互的依赖

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

kotlin协程实现原理 的相关文章

  • 技术分享 | P450-圆框跟踪的干货分享

    圆检测流程 xff1a 1 xff0c 利用高斯滤波做预处理 2 xff0c 边界检测部分用到了自适应Canny检测 3 xff0c 将边界分为凹弧和凸弧 xff0c 根据输入参数筛选弧段 4 xff0c 利用弧段来估计椭圆参数 xff0c
  • Linux Xampp 下安装PHP Redis扩展

    cd usr local wget http pecl php net get redis 4 0 1 tgz tar zxvf redis 4 0 1 tgz cd usr local wget http mirrors kernel o
  • 技术分享 | Prometheus(P450)-室内外避障

    原理说明 Astar进行全局路径规划 全局路径规划 1 全局算法和局部算法 全局路径规划是在已知的环境中 xff0c 给机器人规划一条路径 xff0c 路径规划的精度取决于环境获取的准确度 xff0c 全局路径规划可以找到最优解 xff0c
  • PX4官方动态 | 基于FPGA和px4的精准自主降落

    近期Ramon Roche在Twitter上发布了关于使用FPGA实现无人机在目标物体上能够进行精准自主着陆的推文 这次的功能实现是一个在读博士的学生所做的项目 xff0c 希望能在开源社区中得到推广并吸引其余有意向的开发者能一起进行研究
  • 技术分享 | 带你具体部署VINS_FUSION_GPU版本

    前期准备工作已经完成 xff0c 接下来我们就准备VINS在NX的落地 1 下载源码编译 首先VINS gpu版本需要引入OpenCV CUDA版本的加速 xff0c 由于我们的NX镜像已经安装好CUDA xff0c 这里就不在赘叙 xff
  • PX4官方动态 | 带你走进官方教学(二)

    本期是我们第二期官方教学 xff0c 不知道大家有没有跟着我们一起学习第一期的教学呢 xff0c 如果没有看过的同学可以点击链接进行第一期的学习后再来看我们第二期内容 在第一期我们告诉了大家学习方法 xff0c 以及如果从零开始使用PX4自
  • 吊舱追踪 | 车机协同作战小实验

    大家好 xff0c 我是阿木实验室的梓衡 xff0c 今天为大家带来的是我们最新研发的智能吊舱的测试 首先 xff0c 我们会把小吊舱挂载在P450无人机上 xff0c 我们所选择的地面追踪目标是我们的R300无人车 这个吊舱搭载了200W
  • 工实小报 | P450室外首飞教学

    本文将给大家介绍我们P450无人机在进行室外首次飞行时 xff0c 一些常见的注意事项及正确的使用方法 我们在使用这种PX4开源无人机时 xff0c 一定要有一个清楚的认识 它和大疆那种消费级无人机在使用和操作上 xff0c 是有非常大的不
  • 5G时代,将为无人机通讯传输带来哪些新变化?

    众所周知 xff0c 我们正在大步迈向5G时代 xff0c 在近几年的各类媒体关于5G的报道也络绎不绝 与此同时 xff0c 无人机作为空中人工智能领域的 智慧眼 xff0c 其行业应用迅速发展 xff0c 应用需求不断增加 xff0c 对
  • 超小型吊舱它来了,轻松适配多种移动机器人使用场景

    一 设计理念 在无人机 无人车 机器狗等移动机器人场景中 xff0c 往往需要一款小巧 重量轻 成本可控的三轴云台吊舱来实现无人机的航拍 机器人搜寻以及图像识别等功能 而市面上的云台吊舱体积都比较大 xff0c 一些小型增稳云台也不支持角度
  • P600旗舰视觉款正式发布,重新定义视觉追踪与精准定位!

    P600旗舰视觉款无人机是一款准行业级无人机 xff0c 搭载RTK定位系统 xff0c 定位精度可达厘米级 xff0c 飞行路径更精准 姿态更稳定 xff1b 机身搭载Allspark机载计算机 xff0c 算力可达21TOPS xff0
  • [STM32学习笔记1]GPIO初始化,点亮LED

    一 使用STM32cubeMX新建工程并初始化 1 打开STM32cubeMX并新建工程 xff0c 芯片输入STM32F103C8T6 双击芯片进入配置界面 xff0c 首先选择调试方式SYS gt debug gt serial wir
  • 开发人员调试IE9默认IE7模式打开

    IE9的默认就是IE9标准模式啊 xff0c 你可能是启用了组策略里的 打开Internet Explorer 7标准模式 功能 禁用就行 开始菜单 运行 输入gpedit msc后确定 用户设置 管理模板 Windows组件 Intern
  • visual studio屏蔽掉一段代码的组合键

    注释 xff1a Ctrl 43 k 43 c 取消注释 xff1a Ctrl 43 k 43 u
  • 如何在VS Code中运行C或C++程序

    前言 众所周知 xff0c VS Code源代码编辑器 xff0c 是目前最为流行的代码开发工具之一 xff0c 特别受到Web前端开发者的青睐 xff0c 当然还有大名鼎鼎的HBuilder X也是非常给力的 xff0c 我们可以根据自己
  • C语言指针作为形参的一些问题

    C语言中指针是个非常麻烦的事件 xff0c 本人大学学了几年指针 xff0c 用起来还是丈二和尚 xff0c 摸不着头脑 xff0c 特别是在函数中作为参数传递 xff0c 申请空间什么的 xff0c 一头雾水 xff0c 看到这篇文件写的
  • visual studio的cpp文件添加c文件的extern变量出错原因解析

    比如extern这个变量报这个错 xff1a 说这个来自c文件的extern变量为无法解析的外部符号 可以考虑将cpp文件后缀名改成c文件 xff0c 或者加个extern 34 C 34 就可以解决了 xff01 xff01
  • 一文带你全面解析postman工具的使用(效率篇)

    说明 xff1a 由于前面的一文篇幅太大 xff0c 导致无法放在一文发布 xff0c 故这篇文章只是postman工具介绍的第二部分 xff0c 接下来介绍的内容是基于上文的基础往下进行的 三 postman快捷功能 在这一个部分中 xf
  • stm32cube生成串口代码纪要

    这里只讲思路 xff0c 不讲具体代码 第一步 xff1a static void MX USART1 UART Init void xff1b 函数为stm32cube 自动生成 xff0c 只是用于配置串口传输格式 xff0c 波特率等
  • (完整版)Python读取CANalyst-II分析仪(创芯科技)接口函数

    根据需求 xff0c 需要读取CAN总线的信息 目前市面上主流的做法是 xff0c 通过ZLG周立功的CAN设备来进行读取 由此 xff0c 派生出很多小品牌 xff0c 其设备的基本用法和ZLG非常相似 xff0c 本文以创芯科技的CAN

随机推荐

  • rostopic pub的一些问题汇总解决

    1使用rostopic pub给 cmd vel topic指定了速度 xff0c 但是gazebo中的模拟机器人并未发生运动 介绍 xff1a 打开模拟器 模拟器打开 记录位置 执行命令 xff0c 就是手动控制小车运动的 查看节点 xf
  • ROS中显示inf问题(激光雷达laserscan rviz)

    需要改动两处代码 xff1a robot sim demo urdf sick tim urdf xacro 文件 xff1a 将带有 xff02 gpu xff02 的那一行注释掉 xff0c 使用非 xff02 gpu xff02 那一
  • 阿里云 开通 https CDN 客户端和服务器不支持常用的 SSL 协议版本或加密套件

    需要在CDN管理配置上添加https证书 HTTPS设置
  • 1e-7

    1e 7 就是10的负7次方
  • 3维空间中点、线、面之间的数学关系(python代码)

    1 面的定义 三维空间中的平面由两个量确定 xff1a 一个法向量 xff08 垂直于该平面的向量 xff09 一个已知点 xff08 位于该平面上的一个点 2 叉乘和点乘的区别 2 1叉乘的计算方式 xff0c 叉乘用来得到垂直于两条向量
  • 三维空间中,向量在另外一个向量或者面上的投影

    1 向量在另外一个向量上的投影 求向量u在向量v上的投影 定义为u xff0c 为两向量的夹角 一个向量有两个属性 xff0c 大小和方向首先明确向量点乘的含义 u v
  • C++编程02(引用、参数传递、内联函数)

    C 43 43 编程02 xff08 引用 参数传递方式 内联函数 xff09 引用 文章目录 C 43 43 编程02 xff08 引用 参数传递方式 内联函数 xff09 一 引用1 引用的实质2 引用必须初始化3 对数组建立引用 二
  • 使用Git bash查看之前版本和恢复最新版本的方法

    项目场景 xff1a 作为实习生 xff0c 一直在帮公司搞C 43 43 的图像识别项目 xff0c 因为总是改需求 xff0c 所以我的代码注释的一大片 xff0c 不便于整理 xff0c 有时候改的改的 xff0c 就找不到之前的版本
  • Windows平台下CMake使用报错No CMAKE_CXX_COMPILER could be found

    今天在笔记本建立了一个Cmakelists xff0c 使用cmake编译的时候 xff0c 报错 No CMAKE CXX COMPILER could be found No CMAKE C COMPILER could be foun
  • Cartographer编译方法及编译出错(glog库链接错误)解决方法

    最近在重新调试Carto代码 xff0c 想把自己的代码加入到Carto中 xff0c 原本想在IDE中调试 xff0c 然而Carto编译方式比较奇葩 xff08 catkin make isolated install use ninj
  • 什么是RTOS?RTOS与普通操作系统的区别

    一 xff1a 什么是RTOS RTOS Real Time Operating System xff0c 实时操作系统 实时性是其最大特征 xff0c 实时操作系统中都要包含一个实时任务调度器 xff0c 这个任务调度器与其它操作系统的最
  • 进度条(CSS)

    效果 xff08 实际有动态效果 xff09 html span class token tag span class token tag span class token punctuation lt span div span span
  • 终端命令行打开vscode

    在指定文件夹内 xff0c 使用终端命令行打开vscode 在当前目录打开vscode code 如果code命令无法使用 xff0c 需要配路径 xff0c 如下 xff1a 打开bash或者zsh配置文件 bash 用户请使用 vi b
  • 串口缓冲区管理分析

    一 概述 xff1a 串口使用时一般包含两个缓冲区 xff0c 即发送缓冲区和接收缓冲区 发送数据时 xff0c 先将数据存在发送缓冲区 xff0c 然后通过串口发送 xff1b 接收数据时 xff0c 先将接收的数据存在接收缓冲区 xff
  • array element has incomplete type

    http stackoverflow com search q 61 luaL reg 43 incomplete 43 type libs edje lua2 o edje lua2 c 183 error array type has
  • rt-thread学习记录(一)--内核的移植

    rt thread学习记录 xff08 一 xff09 内核的移植 1 基本选择 在rt thread官网上 xff0c 看到其对stm32芯片的支持 xff0c 因此选择stm32c8t6最小系统来进行移植内核 xff0c rt thre
  • rt-thread内核启动分析

    1 项目准备 上一节的基本环境 如rh thread 基本环境的搭建 硬件材料stm32f103C8T6 以及st link rt thread 内核启动官网分析 在分析rt thread代码的时候 由于rt thread的代码是十分优秀的
  • 达梦数据库sql语句记录

    登入 在ubuntu上安装好达梦数据 xff0c 并且生成实例 xff0c 在ubuntu上进行安装目录 xff0c 采用在tools目录下 xff0c 使用 disql进行命令行模式 xff0c 连接服务器 xff1a Conn sysd
  • vertx web开发(一)

    vertx web开发 最近在开发中 xff0c 由于spring 的大而全 xff0c 反而不实用于一下小项目 xff0c 因为spring boot在空载的情况 xff0c 至少其内存占用超过150M 而对于一些简单的项目反到不适用 而
  • kotlin协程实现原理

    传统runnable接口实现 在java中 xff0c 很多耗时的行为通过实现runnable接口 xff0c 并且通过线程运行下这些耗时的任务 xff0c 例如 xff1a span class token keyword public