关于 异步 的一些思考

361 阅读5分钟

前置说明

  • 任务: 做一件事
  • 任务块: 做这件事情的一个步骤

例如:

任务: 在控制台打印 Node.js 的 Wiki

任务块1 任务块2 任务块3
构建 Wiki 地址(https://en.wikipedia.org/wiki/Node.js) 向这个地址发起网络调用 在控制台打印返回的内容

我对 异步 的认识

良好的异步程序设计

  • 尽可能地将与计算无关的 任务块 (如 上边例子中的 任务块2)托付给运行时
  • 避免这些 任务块 对当前线程的阻塞
  • 使得当前线程可以执行其它 任务任务块
  • 从而达到并发的目的

上边的认识真的完整吗?

将与计算无关的 任务块 托付给了谁, 它为什么可以承受?

  1. 运行时将这些任务块托付给了操作系统, 操作系统将它们托付给了硬件, 而硬件执行自己的工作不占用 CPU 时间
  2. 操作系统不会来回去问硬件,“你准没准备好” “你准没准备好” “你准没准备好”...., 而是硬件在准备好的时候会给操作系统一个信号
  3. 在等待信号的这段时间内, 这个线程对 CPU 的占用率甚至可以达到0, 并且这个占用率并不随着它等待的事情的增加而明显增加
  4. 可以说, 线程在托付 任务块 之后就忘了它曾经把一个 任务块 托付出去这一情况了
  5. 操作系统把内容准备好之后会通知 运行时, 运行时 把内容交给 需求这个内容的任务第一个 任务块(为什么这么说 可以看下面对 调用者 的解读 最开始的任务块(调用异步函数的函数)如果不阻塞调用根本不会收到提醒, 它甚至都不在了(执行完毕 弹出调用栈))
  6. 任务块 在拿到内容之后进行进一步的处理 (如 上边例子中的 任务块3)

所以最后这个问题落脚点成为 硬件为什么可以承受? 看完下面的内容之后我们再说

怎样理解阻塞非阻塞与同步异步的区别?

举一个网络上的例子:

老张爱喝茶,废话不说,煮开水。出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。

  1. 老张把水壶放到火上,立等水开。(同步阻塞) 老张觉得自己有点傻
  2. 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞(其实是部分阻塞, 在去厨房看的过程中是阻塞的)) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
  3. 老张把响水壶放到火上,立等水开。(异步阻塞) 老张觉得这样傻等意义不大
  4. 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)

老张觉得自己聪明了。

所谓同步异步,只是对于水壶而言:

  • 普通水壶,同步;
  • 响水壶,异步。

虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。

同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。

所谓阻塞非阻塞,仅仅对于老张而言:

  • 站在水壶旁边等的老张,阻塞;
  • 看电视的老张,非阻塞。

情况1和情况3中老张就是阻塞的。

虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

网上的例子常说的通知调用者,这个调用者指的是谁?

网上的例子常说(比如上面烧水的例子), 系统在准备好内容之后通知调用者。

如果我们没有在调用异步函数之后阻塞的获取值(如 Future.get ), 则那个(调用异步函数的)函数调用完异步函数之后就执行其他的事情去了, 把其他事情做完这个函数就从 调用栈 中被弹出,它根本不会关心这个异步函数成功拿到值之后怎么办,异常怎么办。

所以这个 调用者 指的是(调用异步函数的)函数所在的运行时

异步函数是一个把

  • 从 IO 拿到值的工作
  • 拿到值之后来的处理步骤(函数)
  • 异常处理机制(函数)

这三者封装在一起的闭包

把闭包交给运行时去做, 运行时把 从 IO 拿到值的工作 托付系统,同时把

  • 拿到值之后来的处理步骤(函数)
  • 异常处理机制(函数)

这两个步骤保存下来

如果系统成功的返回了值 就把值交给第一个函数,否则执行第二个

既然后续的运行机制都交代清楚了, (调用异步函数的)函数就可以安心的离开了 :)

总结

正如 异步阻塞 的情况中所描述的, 我们的硬件其实完全可以承受住大量的工作。但是我们原先的做法是创建多个线程, 这些线程在等待硬件返回内容之前一直存在,导致大量的线程上下文切换耗尽了 CPU。(使用阻塞函数所带来的)性能瓶颈, 从来不在硬件上。

系统底层不会阻塞,内核都是异步的。只不过,我们却因为习惯使用同步函数,选择像老张一样站在一个响水壶的旁边。

其实,是先有异步,同步是后封装的啊

参考链接