1 协程的概念
协程,英文Coroutines,是一种比线程更加轻量级的存在。
协程不是进程,也不是线程,它就是一个可以在某个地方挂起的特殊函数,并且可以重新在挂起处继续运行。所以说,协程与进程、线程相比,不是一个维度的概念。
一个进程可以包含多个线程,一个线程也可以包含多个协程,也就是说,一个线程内可以有多个那样的特殊函数在运行。但是有一点,必须明确,一个线程内的多个协程的运行是串行的。如果有多核CPU的话,多个进程或一个进程内的多个线程是可以并行运行的,但是一个线程内的多个协程却绝对串行的,无论有多少个CPU(核)。这个比较好理解,毕竟协程虽然是一个特殊的函数,但仍然是一个函数。一个线程内可以运行多个函数,但是这些函数都是串行运行的。当一个协程运行时,其他协程必须挂起。
2 协程和线程、进程
一开始单核CPU背景下,操作系统最早也是单个进程的运行,只有执行完一个进程后关闭,再次打开下一个进程。
CPU的大部分时间是浪费的,比如“DOS系统”。
为了避免CPU浪费,线程的逻辑就有了, 每个进程分配若干线程,线程之间共享进程资源,其中又有主线程和其他(子)线程之分,子线程干完计算的任务,空闲时间就释放掉CPU占用,供给其他进程可调用,进程留给用户展示和停留使用主线程,这样页面不动(浏览文本等)也不耽误其他进程后台使用CPU。这样的线程在单核CPU上依然是并发不是并行。
后来CPU有了多核,每个核心能处理一个线程数据,这样多线程之间必然出现并行了,为了解决线程之间抢进程资源和不同步的问题,加入了线程同步的概念。
多线程后,按照时间线那么比线程再细化的什么程要出现了,然后并没有。
现在的问题是随着CPU核心数的增加,多线程应用的操作无论怎样都会有状态同步,同步就会有回调,而传统回调的写法会造成冗余的嵌套。
协程的出现解决了嵌套问题,用同步的方式写异步代码。
3 协程的使用
一个线程内的多个协程是串行执行的,不能利用多核,所以,显然,协程不适合计算密集型的场景。协程适合I/O 阻塞型。
目前各个语言支持的协程
Flutter:
Dart支持协程,使用 async await实现的就是协程
Swift:
Java:
JDK19以前
引入quasar框架实现协程
JDK19以后 虚拟线程
go 语言:
go routine 和 go channel
等等
Kotlin的协程
协程的导包
Kotlin 协程版本表
kotlin标准库以及协程库对应关系列表
发布时间 kotlin标准库 官方推荐的协程库版本 标准库更新版本简述
2020-04-15 1.3.72 1.3.8 Kotlin 1.3.70 的错误修复版本。
2020-08-17 1.4.0 1.3.9 具有许多功能和改进的功能版本,主要关注质量和性能。
2020-09-07 1.4.10 1.3.9 Kotlin 1.4.0 的错误修复版本。
2020-11-23 1.4.20 1.4.1 支持新的 JVM 功能,例如通过调用动态进行字符串连接,改进了 KMM 项目的性能和异常处理,DK 路径的扩展:Path(“dir”) / “file.txt”
2020-12-07 1.4.21 1.4.1 Kotlin 1.4.20 的错误修复版本
2021-02-03 1.4.30 1.4.2 新的 JVM 后端,现在处于 Beta 版;新语言功能预览;改进的 Kotlin/Native 性能;标准库 API 改进
2021-02-25 1.4.31 1.4.2 Kotlin 1.4.30 的错误修复版本
2021-03-22 1.4.32 1.4.3 Kotlin 1.4.30 的错误修复版本
2021-05-05 1.5.0 1.5.0-RC 具有新语言功能、性能改进和进化性更改(例如稳定实验性 API)的功能版本。
2021-05-24 1.5.10 1.5.0 Kotlin 1.5.0 的错误修复版本。
2021-06-24 1.5.20 1.5.0 默认情况下,通过 JVM 上的调用动态进行字符串连接;改进了对 Lombok 的支持和对 JSpecify 的支持;Kotlin/Native:KDoc 导出到 Objective-C 头文件和更快的 Array.copyInto() 在一个数组中;Gradle:缓存注解处理器的类加载器并支持 --parallel Gradle 属性;跨平台的 stdlib 函数的对齐行为
2021-07-13 1.5.21 1.5.0 Kotlin 1.5.20 的错误修复版本。
2021-08-23 1.5.30 1.5.1 JVM上注解类的实例化;改进的选择加入要求机制和类型推断;测试版中的 Kotlin/JS IR 后端;支持 Apple Silicon 目标;改进的 CocoaPods 支持;Gradle:Java 工具链支持和改进的守护程序配置;
2021-09-20 1.5.31 1.5.2 Kotlin 1.5.30 的错误修复版本。
2021-11-29 1.5.32 1.5.2 Kotlin 1.5.31 的错误修复版本。
2021-11-16 1.6.0 1.6.0 具有新语言功能、性能改进和进化性更改(例如稳定实验性 API)的功能版本。
2021-12-14 1.6.10 1.6.0 Kotlin 1.6.0 的错误修复版本。
2022-04-04 1.6.20 1.6.0 具有各种改进的增量版本
2022-04-20 1.6.21 1.6.0 Kotlin 1.6.20 的错误修复版本。
最新
协程的创建方式
创建协程,协程必须运行在协程作用域内。
协程作用域CoroutineScope是协程的运行范围。
有几种方式:
1、runBlocking不常用
使用runBlocking开启个IO的协程。
runBlocking内部就是协程运行的空间,该方式创建的协程会阻塞其他线程运行,运行完成后测试结束才会打印,如下log:
delay2秒后,其他线程才能继续运行。所以基本用到很少,可以作为调试用。
查看runBlocking源码
支持泛型返回值。
也就是直接这么用,显然的异步方法直接传回了结果给外层上下文。
2、launch(MainScope建议使用\GlobalScope不建议使用全局协程),不带泛型返回值
GlobalScope是全局协程空间,启动方式简单,但是内部运行或者子协程运行报错容易造成内存泄漏。
使用MainScope一样简单,使用的时候初始化,不用的时候销毁,cancel方法也会停止协程空间内的所有线程。
创建一个IO线程下的协程,执行test1和test2两个挂起函数。
test1和test2是挂起函数,被suspend修饰,运行到该函数时主协程会挂起等待该函数执行完毕再切换回主协程继续执行。
不被suspend修饰的函数也可以放在主协程内,逻辑和普通函数一样,也可以直接在主协程写方法块。
运行看看
这种方式创建的协程不会阻塞其他线程,如下log:
test1和test2挂起函数,就会顺序执行。
创建协程时,
指定主协程的运行线程,默认运行在主线程。
如果运行网络和文件数据库操作就选择IO,运行耗时计算类就选择Default。
如果想要切换线程可以使用withContext或coroutineScope+launch。
test2改成coroutineScope+launch
或者test2挂起函数改成切换线程
可以看到test2已经在主线程,两个函数顺序执行,结果接近。
launch要特别注意下也是可以创建协程的。coroutineScope+launch是切换协程,
不加coroutineScope就是创建协程,但是launch只能在主协程控件内运行,所以可以理解为子协程。
test1虽然在前面,但是创建主线程的子协程需要异步耗时,所以比test2运行略晚。test2和test1都是delay1秒,所以比主协程完成也要晚,但多个协程,耗时只有1秒。
3、withContext
withContext可以直接创建协程,也可以在协程空间内切换协程,带泛型返回值。
在协程空间内,执行1个或多个withContext是顺序同步执行的。
非协程空间内,就是单独创建协程。
运行在IO线程,test1在IO线程,运行test2切换到主线程,test2完成后切换回主协程回到主线程。
多个withContext按顺序同步执行。
那如果多个耗时任务想要一起跑异步呢? 刚才的launch开辟新的子协程可以实现,还有更好的方式,就是async。
4、async也是协程空间内才可以使用的函数,指定的方法块可以和其他async函数内的并发运行。
一个协程空间内,多个async是并发执行的。
三个1秒的挂起函数同时执行。
3个1秒的delay,只需要1秒都完成了。
await的目的是同步接收结果
不调用await
--------------------------------------------------------------------
其他应用
读取文件
本地assets写一个文件
编写读取文件函数,同步代码无法加挂起修饰符。
IO协程调用该方法,切换到主线程显示内容。
耗时37ms读取文件,55-37ms切换到主线程。
下边试了一个async写法
这么用耗时130ms更慢 为啥?
--------------------------------------------
网络请求
简单封装一个retrofit的,借助云游戏的接口,维护一套kotlin的
定义接口,加suspend挂起函数
利用async开启异步协程,维护一套await扩展支持返回值切换到主线程。
最后调用IO协程
上边是常用写法封装实际使用withContext也可以
其他写法,嵌套、切换怎么写都行
MainScope不是全局的协程,退出页面或者ViewModel中别忘了回收,也可以封装每次请求后自动回收。
End 谢谢大家!
------------------------------------------------------------------------------------------------------------------