为什么需要异步?

Rust写出的程序又快又好,已经很受欢迎了,那为什么还要编写异步的代码呢?

异步代码让我们能够在同一操作系统的线程上并发地运行多个任务。一个有代表性的例子就是,如果要同时下载两个网页,就得把工作分配给两个不同的线程,就像下面这样:

fn get_two_sites() {
    // Spawn two threads to do work.
    let thread_one = thread::spawn(|| download("https://www.foo.com"));
    let thread_two = thread::spawn(|| download("https://www.bar.com"));

    // Wait for both threads to complete.
    thread_one.join().expect("thread one panicked");
    thread_two.join().expect("thread two panicked");
}

确实这么做在很多应用场景下都行得通——毕竟这就是线程的设计目的:一次运行多个不同任务。然而局限性也同样存在:在不同的线程间切换或共享数据会带来很大的开销,即便一个线程不做任何事,也会消耗大量宝贵的系统资源。因而要通过异步的代码设计来消除开销。我们可以通过Rust的async/.await关键字来重写上面的函数,以在不创建多个线程的条件下同时运行多个任务:

async fn get_two_sites_async() {
    // Create two different "futures" which, when run to completion,
    // will asynchronously download the webpages.
    let future_one = download_async("https://www.foo.com");
    let future_two = download_async("https://www.bar.com");

    // Run both futures to completion at the same time.
    join!(future_one, future_two);
}

总的来说,与相应的线程实现相比,异步应用程序可能更快,开销更小,但并不是没有任何开销:线程是受操作系统原生支持的,使用线程不需要特殊的编程模型——任何函数都可以创建一个线程,而调用这些函数就跟调用普通的函数一样。而异步的函数则需要编程语言或库的额外支持。

在Rust中,async fn能够创建一个返回Future的异步函数。为了执行其函数体,必须运行返回的Future来完成任务。

别忘了传统的多线程应用也可以非常高效,而由于Rust的低内存占用和可预测性,即便不使用async也可以做很多事。由于异步编程模型而增加的复杂性不一定值得,因此考虑单纯地用多线程是不是更好,也很重要。