rust-async-p2~> 异步简介
<~~ 发表日期:2021-09-12 | 本文词数:1226 | 预计阅读时间:7分钟 ~~>

来简单介绍一下异步吧

同系列传送门: rust-async

开篇

异步编程 (Asynchronous programming), 是一种并发编程模型
特点是通过少量OS_thread(系统线程), 即可运行大量并发任务, 在某些场景下,可以疯狂压榨cpu的性能


模型对比

为何选择异步,它的优势有哪些? 让我们对比下 异步 与其他 并发模型:

1.系统线程 (OS Thread)
由操作系统提供线程,进行并发,如 std::thread

  • 简单易使用, 建模能力强, 足够传统
  • 操作系统就是运行时, 与C语言交互方便
  • 数据同步困难, 易发生数据竞争
  • 小型任务数量巨大时, 若分别开一个线程, 所需内存与创建线程开销巨大, 性价比极低

2.绿色线程 (Green Thread)
它几乎与系统线程一样, 不过 Runtime(运行时,jvm/.net听说过吗?) 从操作系统变为了程序本身, 由程序本身进行模拟
Rust在1.0版本前删去了它, 因为它不能在语言层面被 Rust 支持, 但可以使用相关库

  • 程序本身对绿色线程有绝对的管理权, 内存分配的粒度更细, 能根据任务大小进行调度, 堆栈可以随时间推移而增长
  • 创建/销毁/切换线程的代价又低又快, 轻松创建成千上万个绿色线程, 来处理任务
  • 由程序本身实现模拟出来, Runtime 较大
  • 如果放在语言层面支持Runtime, 其会一直存在, 即使不使用该特性, 也会拖累性能, 难以与C库交互
  • 绿色线程在不同平台上,其实现可能不同
  • 对多平台的支持/维护/改进得靠实现者保证

异步

我们以IO操作为例子, 假设我们正在单线程下, 读取文件, 并进行一个计算:

let file = /*  */
let content = read(file);
compute()

读取 file, 获取 content, 在此期间, 我们做不了任何其他事, 得等待IO操作(Input/Output, 输入/输出) 完毕, 才能进行接下来的 compute
读取文件, 属于IO操作, 众所周知, IO操作很耗时, 等待它完成的时间可能会很长, 可以使用系统线程加速:

let file = /*  */
// 为每个读取任务新建线程, 并获取句柄
let handle_a = thread::spawan(|| read(file) );
let handle_b = thread::spawan(|| compute() );
// 使用 join 阻塞调用线程, 确保句柄线程执行结束
let content = handle_a.join().unwrap();
handle_b.join().unwrap();

两个任务 readcompute 是独立无依赖的, 所以我们spawn了两个线程

  • 第一个用于读取文件,获取数据
  • 第二个用于执行 compute

速度虽然上去了, 但代价就是额外创建两个线程的开销, 而之后的异步仅依靠单线程, 也能完成上面的操作
单线程中的异步(伪)代码如下:

let file = /*  */
let content = read_async(file);
compute_async()

你会注意到:

  • read -> read_async
  • compute -> compute_async

伪代码表示, 这两操作变成了异步的, 现在你无需关注这是如何变成异步的, 此处仅为为说明异步的概念:

  • read:
    它会阻塞调用线程,等待该操作完毕后, 才继续往下执行 compute
    阻塞期间是几乎不需要 cpu 参与运行的, 程序此时傻乎乎地等待阻塞结束, 无疑是对性能的浪费, 对cpu的不尊重

  • read_async:
    它处于阻塞时,会运行 compute_async, 线程由 read_async 接管, 变为由 compute_async 接管
    当阻塞时间结束,则程序继续变为运行read_async

瞧, read_async 操作阻塞, 无法取得进展时, 其他异步任务将被执行, 由其他异步任务接管线程
总而言之, 异步其实就是当前操作处于阻塞时, 能运行其他不阻塞的操作 (如果有), 使得cpu一直处于运算, 性能被极限压榨
异步操作, 就像是可以随意 start/stop 一般, 调度程序能进行调度, 决定当前要运算哪个, 确保不会因阻塞而傻傻等待

即使是单线程, 也能做到同时运行多个 Task(异步任务), 而异步结合多线程也是可以的, 只要存在耗时的IO操作
如果是非IO密集型, 而是计算密集型, 建议直接使用多线程, 不然就用异步


尾声

Q:
阻塞时期还能同时进行其他操作? IO操作不会占用cpu吗?
A:
现代电脑发展出了一些分担IO操作压力的高科技, cpu对IO操作的压力较小, IO时能进行其他运算, 只要最后接收数据时出个场就行了
所以我们的程序不需要在IO阻塞时傻傻等待, 给它们找点事情做吧!