同步与异步
程序的运行是出于满足人们对某种逻辑需求的处理,在计算机上表现为可执行指令,正常情况下我们期望的指令是按逻辑的顺序依次执行的,而实际情况由于某些指令是耗时操作,不能立即返回结果而造成了阻塞,导致程序无法继续执行。这种情况多见于一些io操作。这时,对于用户层面来说,我们可以选择stop the world,等待操作完成返回结果后再继续操作,也可以选择继续去执行其他操作,等事件返回结果后再通知回来。这就是从用户角度来看的同步与异步。
从操作系统的角度,同步异步,与任务调度,进程间切换,中断,系统调用之间有着更为复杂的关系。
同步I/O 与 异步I/O的区别
为什么使用异步
用户可以阻塞式的等待,因为人的操作和计算机相比是非常慢的,计算机如果阻塞那就是很大的性能浪费了,异步操作让您的程序在等待另一个操作的同时完成工作。三种异步操作的场景:
- I/O操作:例如:发起一个网络请求,读写数据库、读写文件、打印文档等,一个同步的程序去执行这些操作,将导致程序的停止,直到操作完成。更有效的程序会改为在操作挂起时去执行其他操作,假设您有一个程序读取一些用户输入,进行一些计算,然后通过电子邮件发送结果。发送电子邮件时,您必须向网络发送一些数据,然后等待接收服务器响应。等待服务器响应所投入的时间是浪费的时间,如果程序继续计算,这将得到更好的利用
- 并行执行多个操作:当您需要并行执行不同的操作时,例如进行数据库调用、Web 服务调用以及任何计算,那么我们可以使用异步
- 长时间运行的基于事件驱动的请求:这就是您有一个请求进来的想法,并且该请求进入休眠状态一段时间等待其他一些事件的发生。当该事件发生时,您希望请求继续,然后向客户端发送响应。所以在这种情况下,当请求进来时,线程被分配给该请求,当请求进入睡眠状态时,线程被发送回线程池,当任务完成时,它生成事件并从线程池中选择一个线程发送响应
计算机中异步的实现方式就是任务调度,也就是进程的切换
任务调度采用的是时间片轮转的抢占式调度方式,进程是任务调度的最小单位。
计算机系统分为用户空间
和内核空间
,用户进程在用户空间,操作系统运行在内核空间,内核空间的数据访问修改拥有高于普通进程的权限,用户进程之间相互独立,内存不共享,保证操作系统的运行安全。如何最大化的利用CPU,确定某一时刻哪个进程拥有CPU资源就是任务调度的过程。内核负责调度管理用户进程,以下为进程调度过程
在任意时刻, 一个 CPU 核心上(processor)只可能运行一个进程
每一个进程可以包含多个线程,线程是执行操作的最小单元,因此进程的切换落实到具体细节就是正在执行线程的切换
Future
Future<T> 表示一个异步的操作结果,用来表示一个延迟的计算,返回一个结果或者error
,使用代码实例:
Future<int> future = getFuture();
future.then((value) => handleValue(value))
.catchError((error) => handleError(error))
.whenComplete(func);
future可以是三种状态:未完成的
、返回结果值
、返回异常
当一个返回future对象被调用时,会发生两件事:
- 将函数操作入队列等待执行结果并返回一个未完成的Future对象
- 函数操作完成时,
Future
对象变为完成并携带一个值或一个错误
首先,Flutter事件处理模型为先执行main函数,完成后检查执行微任务队列Microtask Queue
中事件,最后执行事件队列Event Queue
中的事件,示例:
void main(){
Future(() => print(10));
Future.microtask(() => print(9));
print("main");
}
/// 打印结果为:
/// main
/// 9
/// 10
基于以上事件模型的基础上,看下Future提供的几种构造函数,其中最基本的为直接传入一个Function
:
factory Future(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
Timer.run(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}