当前位置: 首页 > news >正文

不用休眠的 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() 的明显差异,感谢阅读😃。

相关文章:

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

本文翻译自&#xff1a; https://blog.shreyaspatil.dev/sleepless-concurrency-delay-vs-threadsleep 毫无疑问&#xff0c;Kotlin 语言中的协程 Coroutine 极大地帮助了开发者更加容易地处理异步编程。该特性中封装的诸多高效 API&#xff0c;可以确保开发者花费更小的精力去…...

在Ubuntu中批量创建用户

一、背景知识 在Linux操作系统中创建新用户可以使用useradd或adduser命令。 使用useradd命令创建用户时&#xff0c;不会在/home目录下创建用户文件夹&#xff0c;需要用户自己指定主目录和bash目录的位置。同时&#xff0c;创建的用户没有设置密码&#xff0c;无法进行登录&a…...

汽车冲压车间的RFID技术设计解决方案

一、RFID技术的基本原理 RFID技术是一种利用非接触式自动识别的技术&#xff0c;通过将RFID标签放置在被识别物品上&#xff0c;并使用RFID读写器对标签进行扫描和识别&#xff0c;实现对物品的自动识别和追踪。RFID标签分为被动式和主动式两种。被动式标签无内置电源&#xf…...

TCP 和UDP通信流程

TCP 通信流程 根据上图可以看到&#xff0c;TCP 服务器和客户端通信分为 TCP 服务端和客户端&#xff0c;需要先建立服务 端然后再建立客户端与之连接进行数据交互。 服务端编程步骤&#xff1a; 1.使用 socket 创建流式套接字 2.使用 bind 绑定将服务器绑定到 IP 3.listen…...

Swift SwiftUI CoreData 过滤数据 1

Xcode: Version 14.3.1 (14E300c) iOS: 16 预览&#xff1a; Code: import SwiftUI import CoreDatastruct TodosSearch: View {State private var search_title "测试"FetchRequest var todos_search: FetchedResults<Todo>init() {let request: NSFetchReq…...

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

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

Unity编辑器拓展-Odin

1.相比于原生Unity的优势 Unity不支持泛型类型序列化&#xff0c;例如字典原生Unity不支持序列化&#xff0c;而Odin可以继承序列化的Mono实现功能强大且使用简单&#xff0c;原生Unity想实现一些常见的功能需要额外自己编写Unity扩展的编码&#xff0c;实现功能只需要加一个特…...

小红书婴童产业探索,解析消费者需求!

在消费升级、市场引导的背景下&#xff0c;众多产业都在悄然发生着变化&#xff0c;其中“婴童产业”就是非常有代表性的一个。今天就来深入分析小红书婴童产业探索&#xff0c;解析消费者需求&#xff01; 一、何为婴童产业 事实上&#xff0c;婴童产业&#xff0c;并不仅仅局…...

离线安装mysql客户端

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

Docker 数据管理

管理 Docker 容器中数据主要有两种方式&#xff1a; 数据卷&#xff08;Data Volumes&#xff09; 数据卷容器&#xff08;DataVolumes Containers&#xff09;。 数据卷 数据卷是一个供容器使用的特殊目录&#xff0c;位于容器中。可将宿主机的目录挂载到数据卷上&#xf…...

数据统计--图形报表--ApacheEcharts技术 --苍穹外卖day10

Apache Echarts 营业额统计 重点:已完成订单金额要排除其他状态的金额 根据时间选择区间 设计vo用于后端向前端传输数据,dto用于后端接收前端发送的数据 GetMapping("/turnoverStatistics")ApiOperation("营业额统计")public Result<TurnoverReportVO…...

【kubernetes的三种网络】

kubernetes的三种网络 一、三种网络service网络&#xff08;service是虚拟IP地址&#xff09;pod网络&#xff08;pod的IP地址 docker容器的IP&#xff09;节点网络&#xff08;网络服务器上的物理网卡IP&#xff09; 二、其他网络flannel一、vxlan(隧道方案)1.定义2.优势3.工作…...

Java中树形菜单的实现方式(超全详解!)

前言 这篇文中&#xff0c;我一共会用两种方式来实现目录树的数据结构&#xff0c;两种写法逻辑是一样的&#xff0c;只是一种适合新手理解&#xff0c;一种看着简单明了但是对于小白不是很好理解。在这里我会很详细的讲解每一步代码&#xff0c;主要是方便新人看懂&#xff0…...

基于Uniswap V3的去中心化前端现货交易平台Oku正式登陆Moonbeam

波卡上的Uniswap v3合约由Moonbeam智能合约、Oku前端&#xff0c;以及Wormhole远程路由技术共同实现。 跨链互连应用的最佳去中心化开发平台Moonbeam宣布Uniswap现已正式登陆。此次是Uniswap产品作为一个主流的DEX首次涉足Polkadot生态。用户可以通过新的、易于使用的Oku界面与…...

leetcode 每日一题复盘(10.9~10.15)

leetcode 101 对称二叉树 这道题一开始想是用层序遍历,看每一层是否都对称,遇到一个问题就是空指针(子树为空)无法记录下来,同时会导致操作空指针的问题,因此需要修改入队条件,并用一个标志去表示空指针 vector<int>numv;for(int i0;i<size;i){TreeNode*frontque.fro…...

【云计算网络安全】DDoS 缓解解析:DDoS 攻击缓解策略、选择最佳提供商和关键考虑因素

文章目录 一、前言二、什么是 DDoS 缓解三、DDoS 缓解阶段四、如何选择 DDoS 缓解提供商4.1 网络容量4.2 处理能力4.3 可扩展性4.4 灵活性4.5 可靠性4.6 其他考虑因素4.6.1 定价4.6.2 所专注的方向 文末送书《数据要素安全流通》本书编撰背景本书亮点本书主要内容 一、前言 云…...

如何巧用AI智能技术,让文物不再“无人问津”?

文物是文化与传统的象征&#xff0c;而博物馆则是展现文物的载体。传统的博物馆监控体系只是利用摄像头进行监控&#xff0c;无法将人工智能融入其中&#xff0c;使其更加智能化、信息化。那么&#xff0c;如何将AI技术与传统视频监控相融合呢&#xff1f;TSINGSEE青犀智能分析…...

一天一八股——SSL/TLS协议

早期设计的http协议存在诸多的问题&#xff0c;SSL/TLS在http的基础上保证了数据的保密&#xff0c;验证和身份验证 https的保密性通过混合加密的方式保证&#xff0c;解决窃听问题https数据的完整性通过摘要算法保证&#xff0c;通过数字证书CA的方式进行数据来源和数据可靠性…...

SpringCloud学习笔记-Eureka服务的搭建

目录 1.首先引入依赖2.main中配置注解3.src/main/resources/application.yml配置文件 本文的主要工作是介绍如何搭建一个Eureka服务 1.首先引入依赖 pom文件中加入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring…...

css如何实现页面布局与五种实现方式

CSS布局实现的主要方式有以下几种&#xff1a; 一、盒模型布局&#xff1a;CSS中&#xff0c;每个元素都是一个盒子&#xff0c;包括内容、内边距、边框和外边距。通过设置盒子的属性&#xff08;如宽度、高度、内边距、边框、定位等&#xff09;&#xff0c;可以实现不同的布…...

cv2.split函数与cv2.merge函数

split函数用于图像BGR通道的分离 merge函数用于可将分开的图像通道合并到一起 1.split函数的使用 这是原图&#xff0c;我们使用split函数对其三个通道进行分离。 注意&#xff1a;split函数分离通道的顺序是B、G、R。 以下方法是将三个通道的值都设置为与某一个通道相同。…...

Vue--1.7watch侦听器(监视器)

作用&#xff1a;监视数据变化&#xff0c;执行一些业务逻辑或异步操作。 语法&#xff1a; 1.简单写法->简单类型数据&#xff0c;直接监视 const app new Vue({el: #app,data: {words:},watch:{words(newValue,oldValue){}}}) const app new Vue({el: #app,data: {obj…...

序列:全序关系

一个序列满足全序关系必须满足以下条件&#xff1a; 反对称性&#xff1a;若 a ≤ b a\le b a≤b&#xff0c;则 b ≥ a b\ge a b≥a传递性&#xff1a;若 a ≤ b a\le b a≤b 且 b ≤ c b\le c b≤c&#xff0c;则 a ≤ c a\le c a≤c完全性&#xff1a; a ≤ b a\le b …...

100M服务器能同时容纳多少人访问?

100M的服务器带宽能够同时容纳的用户访问量需要考虑以下几个关键因素: &#x1f449;1.单个用户的平均访问流量大小 这取决于网站内容,是否有多媒体等。一般文本类网站每用户每次访问在50-100KB。 &#x1f449;2.每个用户的平均访问页面 通常每次访问会打开多个页面 &…...

Javascript 笔记:函数调用与函数上下文

在 JavaScript 中&#xff0c;函数上下文通常指的是函数在执行时的当前对象的引用&#xff0c;这通常用 this 关键字表示。this 关键字在不同的执行上下文中可能引用到不同的对象。 1 全局上下文 当 this 关键字用在全局上下文&#xff08;不在任何函数内部&#xff09;&#…...

【WebService】C#搭建的标准WebService接口,在使ESB模版作为参数无法获取参数数据

一、问题说明 1.1 问题描述 使用C# 搭建WebService接口&#xff0c;并按照ESB平台人员的要求&#xff0c;将命名空间改为"http://esb.webservice",使用PostmanESB平台人员提供的入参示例进行测试时&#xff0c;callBussiness接口参数message始终为null。 以下是ES…...

Sqlserver关于tempdb临时数据库文件个数的最佳实践

官方文档 https://learn.microsoft.com/zh-cn/sql/relational-databases/databases/tempdb-database?viewsql-server-ver16 https://learn.microsoft.com/en-US/troubleshoot/sql/database-engine/performance/recommendations-reduce-allocation-contention 一般而言&#x…...

【Java】微服务——微服务介绍和Eureka注册中心

目录 1.微服务介绍2.服务拆分和远程调用2.1.提供者与消费者 3.Eureka注册中心3.1.Eureka的结构和作用3.2.Eureka的结构3.3.搭建Eureka服务3.3.1.引入eureka依赖3.3.2.编写配置文件 3.4.服务注册及拉1&#xff09;引入依赖2&#xff09;配置文件3&#xff09;启动多个user-servi…...

C++ virtual 虚函数 虚基类

https://blog.csdn.net/xbb123456rt/article/details/81986691 基类指针可以指向一个派生类对象&#xff0c;但派生类指针不能指向基类对象。 可以在基类中将被重写的成员函数设置为虚函数&#xff0c;其含义是&#xff1a;当通过基类的指针或者引用调用该成员函数时&#xf…...

redis分布式秒杀锁

-- 获取锁标识&#xff0c;是否与当前线程一致&#xff1f; if(redis.call(get, KEYS[1]) ARGV[1]) then-- 一致&#xff0c;删除return redis.call(del, KEYS[1]) end -- 不一致&#xff0c;直接返回 return 0package com.platform.lock;public interface ILock {/*** 获取锁…...

怎么建设商业网站/中国网站建设公司

/^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/...

门户站点是什么/太原百度快照优化排名

文章目录回归问题分类问题快速搭建法保存提取批训练Optimizer优化器回归问题 建立数据集 # 建立数据集 import torch import matplotlib.pyplot as pltx torch.unsqueeze(torch.linspace(-1, 1, 100), dim1) # x data (tensor), shape(100, 1) y x.pow(2) 0.2*torch.rand…...

贵州做网站kuhugz/短视频运营

转自&#xff1a;http://cuijiahua.com/blog/2018/01/dl_3.html 摘要 LeNet-5出自论文Gradient-Based Learning Applied to Document Recognition&#xff0c;是一种用于手写体字符识别的非常高效的卷积神经网络。 一、前言 LeNet-5出自论文Gradient-Based Learning Applied …...

佛山市手机网站建设/免费com网站域名注册

基于.Net Remoting的人品计算器 这几天一直在学习.Net Remoting&#xff0c;关看书不实践确实有点不踏实。想想最近可能没有什么实际项目可以用到这个技术&#xff0c;只好臆想一个项目了。突然想过一个念头&#xff0c;是不是可以做个学生时代一度很流行的“人品计算器”&…...

模版建站/推广计划书范文

<1> 字体设置&#xff1a; /* 单词之间间距 */ letter-spacing:3px; /* 定义以空格间隔文字的间距(就是空格本身的宽度) */ word-spacing:50px; /* 定义文本的大小写状态 capitalize首字母大写 | uppercase大写 …...

免费字体/seo优化网

L2-033 简单计算器 (25 分) 本题要求你为初学数据结构的小伙伴设计一款简单的利用堆栈执行的计算器。如上图所示&#xff0c;计算器由两个堆栈组成&#xff0c;一个堆栈 S 1​ 存放数字&#xff0c;另一个堆栈 S 2​存放运算符。计算器的最下方有一个等号键&#xff0c;每次按…...