来简单介绍一下异步吧
同系列传送门: rust-async
异步编程 (Asynchronous programming), 是一种并发编程模型
特点是通过少量OS_thread(系统线程)
, 即可运行大量并发任务
, 在某些场景下,可以疯狂压榨cpu的性能
为何选择异步,它的优势有哪些? 让我们对比下 异步 与其他 并发模型:
1.系统线程 (OS Thread)
由操作系统提供线程,进行并发,如 std::thread
2.绿色线程 (Green Thread)
它几乎与系统线程一样, 不过 Runtime(运行时,jvm/.net听说过吗?)
从操作系统变为了程序本身, 由程序本身进行模拟
Rust在1.0版本前删去了它, 因为它不能在语言层面被 Rust 支持, 但可以使用相关库
我们以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();
两个任务 read
与 compute
是独立无依赖的, 所以我们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阻塞时傻傻等待, 给它们找点事情做吧!
上一篇: p1~> 系列说明
下一篇: p3~> Future