协程概述:
-
为什么需要协程,协程是什么?
-
为什么需要协程:从操作系统发展历史角度入手:
-
真空管+穿孔卡片
-
工作机制:
- 程序进入输入室得到中间结果,在将中间结果放入输出室,得到最终结果
-
问题:
- CPU利用率极低,从而引入批处理系统
-
-
批处理+晶体管
- 工作机制:收集所有程序写在磁带,将磁带放入计算机,可以使计算机宏观上满载工作
- 问题:程序中存在IO操作时,会引起程序切换,降低性能(涉及到操作系统中断知识),此时引入多道程序
-
多道程序与集成电路
-
工作机制:以进程为单位,降低工作粒度(一个程序通常对应一个进程);当进程1发生IO时,CPU可以处理其他进程(涉及到操作系统进程调度手段,RR调度(CPU轮转机制));但,这仍是一种消极手段并未主动解决程序中IO操作;此时引入,虚拟时间片轮转机制
-
虚拟时间片轮转机制:优先处理IO操作
-
内部维护一个队列,队列中存放存在IO操作的进程;当进程调度时,优先处理这部分存在IO操作的进程;
-
-
-
由此可见,工作粒度降低可以实现程序性能优化;
- 随着并发手段出现,工作粒度从进程--->线程--->协程;
-
-
什么是协程:
-
协程可以看作用户级线程(线程中的函数),是语言的一种特性区别与进程、线程等操作系统概念;Kotlin,golang语言中均有涉及
-
工作机制:
- 以协程为单位分配资源(内存、CPU时间)
- 内部存在管理者:决定那个线程执行
- 内部存在状态机:恢复CPU上下文,使得协程切出去还能切回来(异步对比 JVM中的程序计数器)
-
-
异步程序设计:
-
概述:
- Kotlin协程主要用于构建各类异步程序模型,由此探讨常见异步程序设计思路与模型;
-
同步与异步:指令实际执行顺序与代码编写顺序
-
同步:系统底层决定的
- 程序在任何时候都是同步执行,即使发生中断也只是从一个程序切换到另外一个程序
-
异步:有业务场景决定的
- 代码编写顺序不能满足业务需求,例如延时操作等;
-
-
并发与并行:
-
并行:可以同时运行的进程数(两个咖啡机,两队同时使用)
- 同时执行不同任务,实际上确实是同时执行
- CPU逻辑核心数为8,那么并行度就是8,CPU可以同时执行8个进程(区别物理核心与逻辑核心)
-
并发:不能脱离时间单位,只讨论单位时间的并发量,(例如,一分钟能提供几杯咖啡,两队人交替使用咖啡机,假如一分钟出了四杯咖啡,那么在这一分钟中,并发量为4)
- 应用可以交替执行不同任务,看起来是同时执行(操作系统中部的技术,切得很快)
- 实际上不是同时执行的,只是切得很快,让用户感觉不到
- 时间片轮转机制就是并发的一种手段
-
线程数量由代码与系统共同决定:
-
代码角度:
- 通过线程兴起方式,创建新的线程,理论上无上限
-
系统角度:
- Linux中一个进程最多开1000个线程,一个系统最多同时存在1024个文件描述符
- Windows中一个进程最多开2000个线程
- 使用数据库(MySQL):连接数一般在150-200,但是应用往往使用不止这个
- 线程开多了,导致服务器崩溃
-
虚拟机角度:
- java的本质可简单看做方法调方法;方法封装为栈帧,存入虚拟机栈;而虚拟机栈是有大小的
-
-
-
Kotlin程序的异步性:
-
异步代码不一定立即执行,并且不希望其阻塞主线程(ANR)
- 可能存在耗时任务(IO操作,主动延时)
- thread函数是Kotlin API中对 Java Thread类的封装,调用后默认立即执行线程
-
代码:应该是ACB
println("异步代码") val task = { println("C") } println("A") thread(block = task) println("B") -
执行结果:
-
-
Kotlin 异步代码回调
-
需要拿到异步任务的执行结果,通过回调手段通知调用者
-
代码:
var res = 0; println("异步回调") val callback = { println("异步任务的结果 $res") } val task1 = { res = 3 + 2; callback() } println("计算3+2") thread(block = task1) println("同步代码的结果 $res") -
运行结果:
-
-
Kotlin 异步代码中的回调地狱
- 概述:回调中不断嵌套代码并涉及多个线程间的切换;
- 解决:生产者-消费者模型或者EventBus框架
-
Kotlin 异步代码结果传递:suspend关键字的设计思路
- 异步调用是立即返回的,根据结果就绪状态区分被调用者业务逻辑
- 结果未就绪:被调用者执行异步代码,结果就绪后通过回调传给调用者
- 结果已就绪:被调用者通过回调传给调用者
-
Kotlin 异步代码中取消响应:
- 协作手段,通过interrupt + 检测标志位实现
-
Kotlin 异步代码中的复杂分支:
- 类比 Java中的常见并发工具类
-
Kotlin 协程解决的问题:异步逻辑同步化
-
问题:在异步代码中处理多个异常,可能导致处理异常代码多次调用;
-
解决手段:将异常进行合并,简化代码;
-
常见异步程序设计思路:
-
引入原因:降低异步程序复杂度
- 通过某种手段将异步回调流程与主流程整合,使得异步代码看起来像同步代码
-
Furture:
-
引入时间:JDK1.5引入
-
重要方法:get
- 同步阻塞式返回Furture对应的异步任务结果;
-
缺点:让异步不再是异步,需要在原地等待结果
-
优化:引入CompletableFurture
-
-
CompletableFurture
-
实现了Furture接口
-
get函数的调用仍在在异步环境中,不会阻塞主调用流程,但是让结果获取脱离主调用流程
-
优化手段:
- 自己去整个调用结果,并在合适时机返回
- 引入Promise与async/await
-
-
Promise与async/await
-
Promise:一个异步任务,存在挂起、完成、拒绝三个状态
- 完成状态:结果通过调用then方法的参数进行回调
- 出现异常拒绝时:通过catch方法传入的参数来捕获拒绝的原因
- Promise.all:将多个Promise整合在一起,then消费的就是这个结果
-
async:语法糖
- 放在外部函数声明前,在Promise.all之前加上await;
- 将语法转成我们比较熟悉的代码
-
-
响应式编程:关注数据流的变换与流转,重点在数据输入与输出之间的关系
-
工作机制:
- 输入与输出之间用函数变换来链接,函数之间只对输入输出负责
- 通过将这个函数分发到不同线程上来实现异步,类似于RxJava
-
Single与Observable
-
Single:
- RxJava中提供的一个像Promise的东西
- 只有一个结果
-
Observable:执行取决于订阅而不是立即执行
- 提供了任意变换之间可以切换线程调度器的能力,可以让复杂的数据变换与流转实现异步;当然也导致滥用为线程切换的工具
-
-