Rust 异步并发(一)

前一段时间看了一些关于Rust的异步编程相关的知识,受益匪浅,故做此记录。
Rust异步编程是Rust语言中十分重要特性。现在Rust中的异步编程采用async/.await解决方案。

1. 为什么需要异步编程?

刚开始一直有一个疑问:既然已经有了多线程技术,多用户访问直接使用多线程,为什么还需要异步呢? 后来终于明白,异步是用在多用户同时处理同一资源时候发挥作用的。

现在用100个用户同时修改一个文件来举例:
  • 同步(一般也是阻塞): 程序需要实现读写锁(std::sync::RwLock)互斥锁(std::sync::Mutex),程序会同时产生100个线程,但是只能有一个线程拥有写锁,其他的99个线程均

    • 处于等待状态,线程不会sleep,会循环获取锁(自旋锁Spinlock)。
      • 阻塞的线程还来参与操作系统的抢占式调度,很不科学!为什么不先排好队,用一个线程呢(这也是协程干的事)
    • 或者有起始的加锁开销(通常是对互斥锁:①线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销;②互斥锁在sleep时会陷入到内核态,需要昂贵的系统调用;线程向操作系统请求被挂起是通过一个系统调用,在linux上的实现就是futex)。
  • 异步(一般也是非阻塞): 将100个线程根据一定的先后次序合并成一个线程(也可能是多个线程?), 就可以避免锁的产生,从而减少操作系统对线程调用的开销。但是,当线程之间对资源的依赖关系比较复杂的时候,程序的编写就会十分复杂

    • 协程(coroutine): 实现异步编程的一种方式,是在线程之下的一个单位。协程是语言层面控制数据流的一种“调度”(处理公共资源的时候,相当于用户态的锁), 线程是操作系统层面的“调度”(处理公共资源,使用操作系统或者硬件的锁(即Mutex或者Spinlock))。协程的调度是编译器通过组织运算顺序实现的(通过生成器(等同于一个状态机)实现),线程是通过操作系统来进行抢占式调度的。
    • 协程wiki:协程非常类似于线程。但是协程是协作式多任务的,而线程典型是抢占式多任务的。这意味着协程提供并发性而非并行性。协程超过线程的好处是它们可以用于硬性实时的语境(在协程之间的切换不需要涉及任何系统调用或任何阻塞调用),这里不需要用来守卫关键区块的同步性原语(primitive)比如互斥锁、信号量等,并且不需要来自操作系统的支持。
    • 互斥锁(mutex)的底层原理是什么?
    • 出于什么样的原因,诞生了「协程」这一概念?

所以,异步编程是可以直接用复杂的代码结构直接写出来的。比如上面的例子就是在一个线程里面去依次读写100次。而Rust的async/.await解决方案做的工作是 “将复杂的异步代码 ===> 利用语法糖 ===> 转化为类似于同步的代码(更容易coding)”“用同步的语义解决异步问题”