不用休眠的 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.mzph.cn/news/98422.shtml

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

相关文章

Mysql--内置函数

字符串函数 1、拼接字符串 concat(str1,str2...) select concat(12,34,abccc) select CONCAT(name,的家乡是,hometown) from students 2、包含字符个数 length(abc) 注&#xff1a;一个中文占3个字符&#xff0c;一个字母或数字占1个字符 3、截取字符串 left(str,len)返回字…

持续集成部署-k8s-深入了解 Pod:探针

持续集成部署-k8s-深入了解 Pod:探针 1. 探针分类2. 探针探测方式3. 探针参数配置4. 启动探针的应用5. Liveness 探针的应用6. Readiness 探针的应用1. 探针分类 Kubernetes 中的探针是指容器内的进程用于告知 Kubernetes 组件其自身状态的机制; Readiness Probe:就绪探针用…

Python 爬虫报错分析

在使用requests库进行网络连接时出现了 ConnectionError: (Connection aborted., RemoteDisconnected(Remote end closed connection without response)) 这个错误。这个错误通常发生在以下几种情况&#xff1a; 目标服务器无法访问&#xff1a;可能由于服务器宕机、网络故障…

pytorch中transform库中常用的函数有哪些及其用法?

在PyTorch的torchvision.transforms库中&#xff0c;有许多常用的图像变换函数可用于数据增强和预处理。下面列举了一些常用的函数及其用法&#xff1a; Resize(size): 调整图像大小为给定的尺寸。 transform transforms.Resize((256, 256))RandomCrop(size, paddingNone): 随…

Elasticsearch:使用 huggingface 模型的 NLP 文本搜索

本博文使用由 Elastic 博客 title 组成的简单数据集在 Elasticsearch 中实现 NLP 文本搜索。你将为博客文档建立索引&#xff0c;并使用摄取管道生成文本嵌入。 通过使用 NLP 模型&#xff0c;你将使用自然语言在博客文档上查询文档。 安装 Elasticsearch 及 Kibana 如果你还没…

代理IP与Socks5代理的多重应用

随着数字时代的不断发展&#xff0c;网络工程师们面临着日益复杂的技术挑战。为了应对跨界电商、爬虫、出海业务、网络安全和游戏等多样化领域的需求&#xff0c;网络工程师积极探索并广泛应用代理IP和Socks5代理技术。本文将深入探讨这两项技术在技术领域的多重应用。 1. 跨界…

OpenCV1-VS2022+OpenCV环境配置

OpenCV学习1-VS2022OpenCV环境配置 1.VS、Image Watch、FastStone Image Viewer、OpenCV2.VS2022配置OpenCV环境3.Debug模式下日志的关闭 1.VS、Image Watch、FastStone Image Viewer、OpenCV 1.安装VS2022 2.安装看图拓展&#xff1a;Image Watch for Visual Studio 2022 3…

计算机竞赛 题目:基于深度学习的中文对话问答机器人

文章目录 0 简介1 项目架构2 项目的主要过程2.1 数据清洗、预处理2.2 分桶2.3 训练 3 项目的整体结构4 重要的API4.1 LSTM cells部分&#xff1a;4.2 损失函数&#xff1a;4.3 搭建seq2seq框架&#xff1a;4.4 测试部分&#xff1a;4.5 评价NLP测试效果&#xff1a;4.6 梯度截断…

100天精通Python(可视化篇)——第103天:Pyecharts绘制多种炫酷水球图参数说明+代码实战

文章目录 专栏导读一、水球图介绍1. 水球图是什么?2. 水球图的应用场景二、水球图类配置选项1. 导包2. Liquid类3. add函数三、水球图实战1. 基础水球图2. 矩形水球图3. 圆棱角矩形水球图4. 三角形水球图5. 菱形水球图6. 箭头型水球图7. 修改数据精度8. 设置无边框9. 多个并排…

Safran与是德科技合作为蔚来提供电动汽车中的5G和C-V2X连接测试

概述 虹科Safran GNSS模拟器助力是德科技&#xff08;Keysight&#xff09;为中国顶级电动汽车制造商之一——蔚来汽车&#xff08;NIO&#xff09;提供了业界领先的UXM 5G测试解决方案以验证5G和C-V2X的连接性&#xff0c;能够根据3GPP和其他标准组织定义的最新5G新无线电&am…

解决扬声器异常

之前使用的是PulseAudio PulseAudio 是默认的音频服务器和音频框架&#xff0c;因此大多数应用程序通过 PulseAudio 来处理音频 但也有一些应用程序直接使用 ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;来与音频硬件交互。在这些情况下&#xff0c;ALSA …

深入理解计算机系统(1):系统组成

一、系统硬件组成 1、控制器&#xff08;CPU&#xff09;&#xff1a;解释和执行内存中的指令 &#xff08;1&#xff09;、控制器 程序控制器&#xff1a;指令指针&#xff0c;指向主存中的机器语言指令&#xff0c;为一个字大小的存储设备或寄存器。 指令寄存器、指令译码器、…

离线安装mysql客户端

下载路径 oracle网站总是在不断更新&#xff0c;所以下载位置随时可能变动但万变不离其宗&#xff0c;学习也要学会一通百通。 首先直接搜索&#xff0c;就能找找到mysql官网 打开网站&#xff0c;并点击 DOWNLOADS 往下滚动&#xff0c;找到社区版下载按钮。…

高效解决 TypeError : ‘ numpy._DTypeMeta‘ object is not subscriptable 问题

文章目录 问题描述解决问题 问题描述 解决问题 参考博文 打开报错位置 AppData\Roaming\Python\Python39\site-packages\cv2\typing\ 添加single-quotes&#xff0c;即单引号 博主说The trick is to use single-quotes to avoid the infamous TypeError: ‘numpy._DTypeMeta’…

微信小程序发布流程

前言 上周写了如何写一个小程序&#xff0c;然后经过查资料&#xff0c;改bug&#xff0c;找chatgpt美化页面&#xff0c;我写了一个计算代谢率的小工具&#xff0c;写完了之后该怎么办呢&#xff0c;当然是发布上架&#xff0c;然后我就开始了发布的折腾 提交代码 这一步很…

JavaWeb整体介绍

JavaWeb整体介绍 什么是Java Web Web&#xff1a;全球广域网&#xff0c;也称为万维网&#xff08;www&#xff09;&#xff0c;能够通过浏览器访问的网站JavaWeb&#xff1a;是使用Java技术解决相关web互联网领域的技术栈&#xff08;就是用java开发网站&#xff09; 网页&a…

【uniapp】subnvue组件数据更新视图未更新问题

背景 : 页面中的弹窗使用了subnvue来写, 根据数据依次展示一个一个的弹窗, 点击"关闭"按钮关闭当前弹窗, 显示下一个弹窗 问题 : 当点击关闭时( 使用的splice() ), 数据更新了 , 而视图没有更新, 实际上splice() 是不仅更新数据, 也可以更新视图的 解决 : this.$fo…

WPF中DataContext的绑定技巧

先看效果: 上面的绑定值都是我们自定义的属性,有了以上的提示,那么我们可以轻松绑定字段,再也不用担心错误了。附带源码。 目录 1.建立mvvm项目 2.cs后台使用DataContext绑定 3.xaml前台使用DataContext绑定

MySQL为什么使用B+树而不是跳表

文章目录 B树还是跳表B树简易代码跳表简易代码 B树还是跳表 MySQL的InnoDB存储引擎使用B树而不是跳表&#xff0c;这是因为B树在关系型数据库系统中有一些优势&#xff0c;特别是在处理范围查询、事务处理和数据持久性方面。下面详细说明B树和跳表的底层原理以及它们各自的优缺…

selenium查找网页如何处理网站资源一直加载非常卡或者失败的情况

selenium查找网页如何处理网站资源一直加载失败的情况 selenium获取一个网页&#xff0c;某个网页的资源卡了很久还没有加载成功&#xff0c;如何放弃这个卡的数据&#xff0c;继续往下走 有2钟方式。通常可以采用下面的方式一来处理这种情况 方式一、WebDriverWait 这种方式…