不用休眠的 Kotlin 并发:深入对比 delay() 和 sleep()

在这里插入图片描述

本文翻译自:
https://blog.shreyaspatil.dev/sleepless-concurrency-delay-vs-threadsleep

毫无疑问,Kotlin 语言中的协程 Coroutine 极大地帮助了开发者更加容易地处理异步编程。该特性中封装的诸多高效 API,可以确保开发者花费更小的精力去完成并发任务。一般来说,开发者了解一下如何使用这些 API 就足够了!

可就 JVM 的角度而言,协程一定程度上减少了*“回调地狱”*的问题,切实地改进了异步处理的编码方式。

相信包括笔者在内的很多开发者常常会好奇协程的背后到底是如何做到的。所以,本文将以 delay() 为切入点,带开发者剖析下协程的背后原理。

目录前瞻:

  1. delay() 干啥用的?
  2. sleep() 呢?
  3. 对比 delay() 和 sleep()
  4. 剖析 delay() 原理

1. delay() 干啥用的?

使用过协程的开发者大概率对 delay() 并不陌生,anyway,先来看下官方针对该函数的描述:

“delay() 用来延迟协程一段时间,但不阻塞线程,并且能在指定的时间后恢复协程的执行。”

来看一段在 task1 执行 2000ms 后执行 task2 的示例代码:

scope.launch {doTask1()delay(2000)doTask2()
}

代码很简单,但需要再次提醒一些关于 delay() 的重要特点:

  • 它不会阻塞当前运行的线程
  • 但它允许其他协程在同线程运行
  • 当延迟的时间到了,协程会被恢复并继续执行

很多开发者常常会将 delay() 和 Java 语言的 sleep() 进行比较。可事实上,这两个函数用作完全不同的场景,只是命名上看起来有点相似而已。。。

2. sleep() 呢?

sleep() 则是 Java 语言中标准的多线程处理 API:促使当前执行的线程进入休眠,并持续指定的一段时间

“该方法一般用来告知 CPU 让出处理时间给 App 的其他线程或者其他 App 的线程。”

如果在协程里使用该函数,它会导致当前运行的线程被阻塞,同时也会导致该线程的其他协程被阻塞,直到指定的阻塞时间完成。

为了解更多的细节,让我们通过示例进一步地对比 sleep() 和 delay() 两者。

3. 对比 delay() 和 sleep()

假使我们想在单线程(就比如 Android 开发里的主线程)里执行并发任务。

看一下如下的代码片段:分别启动两个协程,并各自调用了 1000ms 的 delay() 或 sleep()。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

比较:

  • 协程的启动时间:
    • 调用 delay() 代码里的两个协程在同一时间(05:48:58)执行
    • 调用 sleep() 代码里的第 2 个协程相隔了 1s 后执行
  • 协程的结束时间:
    • 调用 delay() 代码里的 2 个协程一共花了 1045ms
    • 调用 sleep() 代码里的 2 个协程则一共花了 2044ms

这也印证了上面提到的特性差异:delay() 只是挂起协程、同时允许其他协程复用该协程,而 sleep() 则在一段时间内直接阻塞了整个线程。

事实上,delay() 还具备其他神奇的特点,再来看看下面的代码示例:

  1. 先定义了一个最大创建 2 个线程的线程池 context 示例

  2. 当第 1 个协程启动并执行一个 task 之后,调用 delay() 挂起 1000ms,接着再执行一个 task

  3. 在第 1 个协程执行的同时,启动第 2 个协程兵执行耗时 task

img

通过查看 task 里打印的 log,我们惊讶地发现:delay 函数执行前,它运行在 Duet-1 线程。但当 delay 完成后,它却恢复到了另一个线程:Duet-2

这是为什么?

原来是因为原线程正在忙于处理第 2 个协程启动的耗时 task,所以 delay 之后它只能恢复到另一个线程。

这就有意思了,看看官方文档的描述。。。

“协程可以挂起一个 thread 并且恢复到另一个 thread!”

既然感受到了 delay() 的魔力,我们就来了解下它背后的工作原理。

4. 剖析 delay() 原理

delay() 会先在协程上下文里找到 Delay 的实现,接着执行具体的延时处理。

public suspend fun delay(timeMillis: Long) {if (timeMillis <= 0) returnreturn suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->if (timeMillis < Long.MAX_VALUE) {cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)}}
}

Delay 是 interface 类型,其定义了延时之后调度协程的方法 scheduleResumeAfterDelay() 等。开发者直接调用的 delay()、withTimeout() 正是 Delay 接口提供的支持。

public interface Delay {public fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>)public fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =DefaultDelay.invokeOnTimeout(timeMillis, block, context)
}

事实上,Delay 接口由运行协程的各 CoroutineDispatcher 实现。

我们知道 CoroutineDispatcher 是抽象类,Dispatchers 类会利用线程相关 API 来实现它。

比如:

  • Dispatchers.DefaultDispatchers.IO 使用 java.util.concurrent 包下的 Executor API 来实现
  • Dispatchers.Main 使用 Android 平台上特有的 Handler API 来实现

接着,各 Dispatcher 还需要实现 Delay 接口,主要就是实现 scheduleResumeAfterDelay() ,去返回指定 ms 之后执行协程的 Continuation 实例。

如下是 ExecutorCoroutineDispatcherImpl 类实现该方法的具体代码:

override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {(executor as? ScheduledExecutorService)?.scheduleBlock(ResumeUndispatchedRunnable(this, continuation),continuation.context,timeMillis)// Other implementation 
}

可以看到:它借助了 Java 包 ScheduledExecutorServiceschedule() 来调度了 Continuation 的恢复。

我们再来看下 Android 平台 Dispatcher 即 HandlerDispatcher 又是如何实现的该方法。

override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {val block = Runnable {with(continuation) { resumeUndispatched(Unit) }}handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY))// Other implementation 
}

它直截了当地使用了 Handler 的 postDelayed() post 了 Continuation 恢复的 Runnable 对象。这也解释了 delay() 没有阻塞线程的原因。

假使你在 Android 主线程的协程里执行了 delay() 逻辑,其效果等同于调用了 Handler 的右侧代码。

img

这种实现非常有趣:在 Android 平台上调用 delay(),实际上相当于通过 Handler post 一个 delayed runnable;而在 JVM 平台上则是利用 Executor API 这种类似的思路。

但如果还是同样的业务逻辑,将 delay() 换成 sleep(),那么效果将大相径庭。可以说,delay() 和 sleep() 是完全不同的两种 API,不要搞混了。

讲到这里,我们能感受到协程的优雅奇妙:用简单的同步代码写出异步逻辑,切实地帮助开发者免受“回调地狱”的困扰。

希望本文能帮你了解到 Kotlin 协程里 delay() 的用法和工作原理,并理解和 sleep() 的明显差异,感谢阅读😃。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

帮助文档Api

帮助文档Api 按照帮助文档的使用步骤学习Scanner类的使用&#xff0c;并实现键盘录入一个字符串&#xff0c;最后输出在控制台 部分Scanner的api文档如下&#xff1a; package com.api.Demo01;// 需要导入 Scanner包 import java.util.Scanner;public class Test01 {public sta…

Altium Designer实用系列(一)----原理图导入PCB、PCB板子外形、多层板绘制等

一、原理图导入PCB 绘制原理图就不必多说了&#xff0c;根据自己电路的需求&#xff0c;去设计电源、芯片的外围电路、MCU外设分配就好。接下来主要介绍的是在导入PCB前对原理图的检查&#xff1a; 元器件标号注解 元器件封装确认&#xff1a;工具->封装管理器&#xff1…

ES6 class类的静态方法static有什么用

在项目中&#xff0c;工具类的封装经常使用静态方法。 // amap.jsimport AMapLoader from amap/amap-jsapi-loader; import { promiseLock } from triascloud/utils; /*** 高德地图初始化工具*/ class AMapHelper {static getAMap window.AMap? window.AMap: promiseLock(AM…

容器运行elasticsearch安装ik分词非root权限安装报错问题

有些应用默认不允许root用户运行&#xff0c;来确保应用的安全性&#xff0c;这也会导致我们使用docker run后一些操作问题&#xff0c;用es安装ik分词器举例&#xff08;es版本8.9.0&#xff0c;analysis-ik版本8.9.0&#xff09; 1. 容器启动elasticsearch 如挂载方式&…

微信小程序:实现列表单选

效果 代码 wxml <view class"all"><view class"item_all" wx:for"{{info}}" wx:key"index"><view classposition {{item.checked?"checked_parameter":""}} data-id"{{item.employee_num}}…

ssm172基于SSM的旅行社管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

COM组件IDispatch操作

IDispatch 组件接口&#xff0c;继承IUnkown&#xff0c;实现了反射机制&#xff0c;可以通过invoke调用dll函数 一般执行过程需要GetIDsOfNames、InvokeHelper函数执行&#xff0c;queryinterface查询获取对象 检查GetIDsOfNames返回的dispid是否正确 COleDispatchDriver 单…

Git指导:提交干净的commit信息

为什么我们应该关心编写干净的提交消息&#xff1f; 提交是程序员技术的有形构建块。它们充当代码的锦上添花&#xff0c;如果编写正确&#xff0c;它们会带来巨大的价值。编写良好的提交消息变得不可或缺&#xff0c;因为它们提供了上下文——否则一开始就不需要提交消息。 良…

Matlab参数估计与假设检验(举例解释)

参数估计分为点估计和区间估计&#xff0c;在matlab中可以调用namefit()函数来计算参数的极大似然估计值和置信区间。而数据分析中用得最多的是正态分布参数估计。 例1 从某厂生产的滚珠中抽取10个&#xff0c;测得滚珠的直径&#xff08;单位&#xff1a;mm&#xff09;为x[…

asp.net闲置物品购物网系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net闲置物品购物网系统是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语 言开发 asp.net 闲置物品购物网 二、功…

el-table 边框颜色修改 简单有效!

废话不多说&#xff0c;直接上图 &#xff08;1&#xff09;修改前的图如下&#xff1a; 以上是elementUI原组件自带的样式 &#xff08;2&#xff09;下面是修改后的边框图如下&#xff1a; 源码如下&#xff1a; <el-table :data"jctableData" border size…

GitLab平台安装中经典安装语句含义解析

yum -y install policycoreutils openssh-server openssh-clients postfix 这是一个Linux命令&#xff0c;用于使用YUM包管理器安装指定的软件包。下面是对这个命令各部分的解释&#xff1a; yum&#xff1a;这是一个Linux命令行工具&#xff0c;用于管理RPM&#xff08;Red …

基于Python和Tkinter的双目相机驱动界面

文章目录 前言准备工作代码分析初始化创建按钮创建图像显示区域创建信息标签启动摄像头捕捉主函数结论效果展示 前言 本文将介绍如何使用Python和Tkinter库来创建一个简单的摄像头应用程序。这个应用程序可以打开摄像头&#xff0c;显示摄像头捕捉的图像&#xff0c;并允许用户…

DruidDataSource导致OOM问题处理

DruidDataSource导致OOM问题处理 起因分析日志分析Dump文件问题分析处理 起因 一个平凡的工作日&#xff0c;我像往常一样完成产品提出的需求的业务代码&#xff0c;突然收到了监控平台发出的告警信息。本以为又是一些业务上的 bug 导致的报错&#xff0c;一看报错发现日志写着…

sed -i 使用变量进行替换

一、替换文本的命令 1、命令&#xff1a; sed -i s/old/new/g xxx.log 例子&#xff1a;将文件1.txt中的字符串 "cores":"" 替换成字符串 "cores":"1" 命令&#xff1a;sed -i s/"cores":""/"…

Unity实现设计模式——模板方法模式

Unity实现设计模式——模板方法模式 模板模式(Template Pattern)&#xff0c; 指在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现&#xff0c;但调用将以抽象类中定义的方式进行。 简单说&#xff0c; 模板方法模式定义一个操作中的算法的骨架&…

ESP32设备驱动-I2C-LCD1602显示屏驱动

I2C-LCD1602显示屏驱动 1、LCD1602介绍 LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。它是由字符型液晶显示屏(LCD)、控制驱动主电路HD44780及其扩展驱动电路HD44100,以及少量电阻、电容元件和结构件等装配在PCB板上而组成。 通过前面的实例我们知道,并口方式…

Godot 脚本外置参数设置

文章目录 添加脚本设置参数bulid 一下 Godot Engine 4.2 简体中文文档 C# exports 添加脚本 设置参数 Godot 添加脚本后&#xff0c;设置参数。两种形式都可以 [Export]public int Speed { get; set; } 10;[Export]public string Name ;bulid 一下 私有变量也可以

基于Java的个性化旅游攻略系统设计与实现(源码+lw+ppt+部署文档+视频讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

公园视频监控系统如何改造?人工智能又能提供哪些帮助?

近日合肥市骆岗公园宣布正式开园&#xff0c;作为目前世界最大的城市公园&#xff0c;占地12.7万平方公里&#xff0c;如此壮观宏伟的建设&#xff0c;也吸引到了不少市民进行参观打卡。不管大型小型&#xff0c;城市里的公园都是随处可见的&#xff0c;那么&#xff0c;公园安…