不止Rust,为什么很多语言都支持异步

48 阅读7分钟

本文来自公众号 猩猩程序员 欢迎关注

经常有一些刚接触编程的学员,搞不明白为什么需要异步,今天通过一篇简单文章解释一下

1.并发的基础——进程与线程

1. 进程 (Process)

  • 是什么:进程是操作系统进行资源分配和调度的基本单位。简单来说,你电脑上运行的每一个程序(比如微信、Chrome浏览器)都是一个独立的进程。
  • 核心特征
    • 资源独立:每个进程都有自己独立的内存空间、数据和资源。一个进程崩溃了,通常不会影响其他进程。
    • 隔离性强:这是优点也是缺点。优点是安全稳定;缺点是进程间通信(IPC)比较复杂且开销大。
  • 比喻一座座独立的工厂。每座工厂有自己的设备、原材料和工人,工厂之间互不干扰。如果想让两个工厂协作,需要通过复杂的物流系统(IPC)来运输物资。

2. 线程 (Thread)

  • 是什么:线程是进程内的一个执行单元,是CPU调度的基本单位。一个进程可以包含多个线程。
  • 核心特征
    • 共享资源:同一进程内的所有线程共享该进程的内存空间和资源(如文件句柄)。
    • 轻量级:创建和切换线程的开销远小于进程。
    • 通信方便:因为共享内存,线程间通信非常简单,但也带来了数据竞争(Race Condition)和死锁(Deadlock)等问题,需要加锁来保护共享数据。
  • 比喻工厂里的工人们。所有工人共享工厂的设备和原材料。多一个工人(线程),就能多一条生产线,提高生产效率。但工人们需要协调(加锁),否则会抢夺同一个设备导致混乱。

2.传统模型的瓶颈 - “等待”的代价

我们有了进程和线程,似乎可以通过“多开工人”的方式无限提升效率。但一个核心问题出现了:等待,或者说 阻塞I/O (Blocking I/O)

  • 什么是阻塞I/O? I/O(Input/Output)指的是输入/输出操作,通常指那些速度远慢于CPU的操作,比如:

    • 读取硬盘文件
    • 发起网络请求(访问网站、查询数据库)
    • 等待用户输入

    当一个线程执行到这些操作时,它必须停下来,“阻塞” 在原地,直到操作完成。此时,CPU是被这个线程霸占着,但它什么也没干,只是在空等。这造成了巨大的资源浪费。

  • 生动的例子:咖啡店的故事

    1. 单线程同步模型(一个笨拙的咖啡师)

      • 一个咖啡师(线程)接待一位顾客。他接到订单后,就去操作咖啡机。在咖啡机制作咖啡的30秒内,他必须站在旁边盯着,不能接待下一位顾客。直到这杯咖啡做好,递给顾客,他才能服务下一个人。
      • 问题:咖啡师(CPU)在等待咖啡机(I/O)工作时,完全被浪费了。
    2. 多线程同步模型(雇佣更多咖啡师)

      • 为了解决排队问题,老板雇了5个咖啡师(线程)。这样可以同时服务5位顾客。
      • 问题
        • 成本高:雇佣更多咖啡师需要更多工资和管理成本(创建和切换线程有开销)。
        • 资源瓶颈:如果店里只有一台咖啡机(I/O资源),那么同一时间还是只有一杯咖啡在做。另外4个咖啡师接到单后,都得排队等着使用咖啡机,他们同样在**“阻塞”等待**。

    无论是单线程还是多线程,只要是同步阻塞模型,“等待” 这个问题就无法根本解决。线程(工人)的大部分时间可能都浪费在等待慢速I/O(设备)上。


3.优雅的解决方案 - 异步编程

异步就是为了解决“等待”的浪费而生的。它的核心思想是:发起一个耗时操作后,不原地等待结果,而是立即返回去做别的事情。当操作完成后,系统会通知我,我再回来处理结果。

  • 异步模型下的咖啡店(一个聪明的咖啡师)

    • 一个聪明的咖啡师(单线程)接待顾客A,收钱下单,然后把指令发给咖啡机(发起I/O操作),并给顾客A一个取餐器(Future/Promise)
    • 不等待咖啡机做完,而是立即去接待顾客B,同样下单、启动机器、给取餐器。
    • 他不断地接待新顾客,直到空闲下来。
    • 突然,顾客A的咖啡做好了,咖啡机发出“滴滴”声(事件通知/Callback)。咖啡师听到后,停下手中的事,把咖啡递给A。
    • 然后他继续做之前的事(比如接待新顾客或响应其他咖啡机的通知)。
  • 异步编程的核心组件:事件循环 (Event Loop) 这个聪明的咖啡师,他的大脑里就在运行一个“事件循环”。

    1. 任务队列:他有一个任务列表(比如“接待新顾客”、“响应机器A”、“响应机器B”)。
    2. 循环检查:他不断地从队列里取任务来执行。
    3. 注册回调:当他发起一个I/O操作(启动咖啡机),他会告诉系统:“这个任务做完后,请把‘处理结果’这个新任务放进我的任务队列里”。
    4. 非阻塞:他永远不会停下来等待,总是在处理任务队列里的事情。
  • 代码示例对比 (Rust)

    use std::time::{Duration, Instant};
    // --- 同步阻塞版本 ---
    // 模拟阻塞的I/O操作
    fn sync_download(file_name: &str, duration_ms: u64) {
        println!("同步:开始下载文件 {}...", file_name);
        std::thread::sleep(Duration::from_millis(duration_ms));
        println!("同步:文件 {} 下载完成", file_name);
    }
    
    fn sync_task() {
        let now = Instant::now();
        sync_download("A", 1000);
        sync_download("B", 2000);
        println!("同步任务总耗时: {:.2?}s", now.elapsed().as_secs_f32());
    }
    
    // --- 异步非阻塞版本 ---
    
    // 模拟非阻塞的I/O操作
    async fn async_download(file_name: &str, duration_ms: u64) {
        println!("异步:开始下载文件 {}...", file_name);
        // 使用 tokio::time::sleep 来模拟非阻塞等待
        // 它会让出CPU控制权,而不是霸占线程
        tokio::time::sleep(Duration::from_millis(duration_ms)).await;
        println!("异步:文件 {} 下载完成", file_name);
    }
    
    async fn async_task() {
        let now = Instant::now();
        // tokio::join! 会并发地运行多个Future
        // 它会等待所有Future完成后再继续
        tokio::join!(
            async_download("A", 1000),
            async_download("B", 2000)
        );
        println!("异步任务总耗时: {:.2?}s", now.elapsed().as_secs_f32());
    }
    
    // 使用 tokio::main 宏来启动异步运行时
    
    #[tokio::main]
        async fn main() {
        println!("--- 执行同步任务 ---");
        sync_task();
        println!("\n--- 执行异步任务 ---");
        async_task().await;
    }
    

4.异步的终极目的

异步的终极目的,不是让代码运行得更快,而是为了最大限度地压榨CPU的性能,提升系统在单位时间内的吞吐量 (Throughput)。

它通过以下方式实现这一目标:

  1. 剥离等待:将CPU从无效的I/O等待中解放出来,让它去做其他有意义的计算任务。
  2. 提高资源利用率:用极少的线程(甚至单线程)来处理海量的并发连接。对于每一个连接,只在真正需要CPU计算时才为其服务,其余时间(网络延迟、数据传输)CPU都在服务其他连接。
  3. 用并发实现高吞吐:在I/O密集型场景下(如Web服务器、数据库代理),异步可以用一个线程实现远超传统多线程模型的并发处理能力。

一句话总结:异步,就是一种用“协作式调度”的思想,让单线程或少数几个线程,通过不停地在不同任务之间切换,来模拟出大规模并发效果的编程模型,其核心是避免CPU在I/O上浪费时间。

总结对比

特性多线程模型异步模型
核心思想多雇工人,一人一件事一人多事,任务间切换
调度方式抢占式(操作系统决定)协作式(代码自己决定何时让出CPU)
开销线程创建/切换开销大开销极小(函数调用)
适用场景CPU密集型(多核并行计算)I/O密集型(高并发网络服务)
编程复杂度复杂(锁、死锁、数据竞争)较复杂(回调地狱、心智模型转换)

本文来自公众号 猩猩程序员 欢迎关注