Rust中的异步编程

39 阅读9分钟

在学习rust的异步编程,阅读async-book遇到不太理解的内容,跟着bilibili上的视频去学习了一遍,因为缺少实际应用,有些点还是迷糊的,在这里简短的做个笔记,方便回头再学习

bilibili视频

async-book

一、一些基本概念

主要是为了明确一些概念,只有概念理解了,才能更好的运用。最后再推荐一个关于讲解rust异步的文档。

1. 什么是进程、线程、协程

进程的概念就相当于我们的程序,运行起来就会开启一个进程,它是资源分配的最小单位。

线程相当于是进程资源的划分,就像公路被划分成多条车道,它是CPU调度执行的最小单位。

协程,这个概念之前一直不明白,查了查deepseek,如果是前端,举个例子Promise的使用,就是协程,在一个线程里,能中断A的执行,去执行B。协程是可以由异步,以及线程来实现的,它可以是多线程的。

2. 什么是并发,并行

并发:就是CPU在多个程序间切换执行,例如Promise.all([A,B]),执行A如果A遇到阻塞,再去执行B,如果B又阻塞,就去再执行A。如果cpu是单核的,即使是多线程运行,它也只能在一个时刻执行一条指令。

并行:它是具有条件的,必须是多核才能并行,就是同一时间,多个程序同时执行。

总结:

  • 并发是软件概念(可以模拟"同时")
  • 并行是硬件概念(需要物理上的多个执行单元)
  • 单核CPU只能实现并发,无法实现真正的并行
  • 多核CPU既可以实现并发,也可以实现并行

3. 并发和异步的区别

异步:是一种并发编程模型,允许在等待 I/O 操作时让出 CPU

原先自己的理解是异步就是并发,查了查deepseek举了下面的例子:

总结起来就是,并发可以由异步来实现,但两者不相等。

// 并发但不异步的例子 - 多线程阻塞IO
use std::thread;
use std::time::Duration;

fn main() {
    // 并发:两个线程同时运行
    let handle1 = thread::spawn(|| {
        std::thread::sleep(Duration::from_secs(2)); // 阻塞
        println!("线程1完成");
    });
    
    let handle2 = thread::spawn(|| {
        std::thread::sleep(Duration::from_secs(1)); // 阻塞
        println!("线程2完成");
    });
    
    handle1.join().unwrap();
    handle2.join().unwrap();
}
// 异步但不并发的例子 - 顺序执行异步任务
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    // 异步但顺序执行(不并发)
    async_task1().await; // 等待第一个完成后再执行第二个
    async_task2().await;
}

async fn async_task1() {
    sleep(Duration::from_secs(2)).await; // 非阻塞等待
    println!("任务1完成");
}

async fn async_task2() {
    sleep(Duration::from_secs(1)).await;
    println!("任务2完成");
}
// 并发 + 异步的例子
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    // 同时启动多个异步任务(并发 + 异步)
    let task1 = async_task1();
    let task2 = async_task2();
    
    // 并发执行异步任务
    tokio::join!(task1, task2);
}

async fn async_task1() {
    sleep(Duration::from_secs(2)).await;
    println!("任务1完成");
}

async fn async_task2() {
    sleep(Duration::from_secs(1)).await;
    println!("任务2完成");
}

4. 并发、线程、协程、异步关系图

并发(Concurrency)
├── 进程(Processes)        // 重量级,操作系统隔离
├── 线程(Threads)          // 中等重量,共享内存  
└── 协程(Coroutines)       // 轻量级,用户态调度
    ├── 异步/await(Rust、JS、Python)
    ├── Goroutines(Go)
    ├── 生成器(Generators)
    └── 绿色线程(Green Threads)

4. rust中的异步

Rust 的异步既可以是单线程的,也可以是多线程的,这取决于使用的运行时(Runtime)和执行器的配置。

单线程运行时

// 使用 tokio 的单线程运行时
#[tokio::main(flavor = "current_thread")]
async fn main() {
    // 所有任务都在单个线程上执行
    let task1 = async { /* ... */ };
    let task2 = async { /* ... */ };
    
    tokio::join!(task1, task2);
}

多线程运行时(默认)

// 使用 tokio 的多线程运行时(默认)
#[tokio::main]
async fn main() {
    // 任务可能在不同的线程上执行
    let task1 = async { /* ... */ };
    let task2 = async { /* ... */ };
    
    tokio::join!(task1, task2);
}

二、理解Send和Sync

  1. Send:通过Send允许在线程间转移所有权

    A type is Send if it is safe to send it to another thread.(如果可以安全地将类型发送到另一个线程,则类型为 Send。)

  2. Sync:允许多线程访问,是通过不可变引用来进行访问的

    A type is Sync if it is safe to share between threads (如果在线程之间共享是安全的,则类型为 Sync)

  3. 如果一个类型完全由 Send 或 Sync 类型组成,那么它就是 Send 或 Sync

附加文档链接:[12]

示例:如下面的例子,因为Rc是没有实现send和sync的,而tikio::spawn入参需要时send的,所以会报错

use std::rc::Rc;

#[derive(Debug)]
struct Handler;

#[tokio::main]
async fn main(){
    let handler = Rc::new(Handler);

    let handler_cloned = Rc::clone(&handler);

    tokio::spawn(process(handler_cloned));
}

async fn process(handler: Rc<Handler>){}

修复,将Rc更改为Arc

use std::rc::Rc;
use std::sync::Arc;

#[derive(Debug)]
struct Handler;

#[tokio::main]
async fn main(){
    let handler = Rc::new(Handler);

    let handler_cloned = Rc::clone(&handler);

    tokio::spawn(process(handler_cloned));
}

async fn process(handler: Rc<Handler>){}

比较容易犯错的地方,和上面第三点有关,虽然Handler是Send的但内部字段是非Send的,所以也就成了非Send的数据

use std::rc::Rc;
use std::sync::Arc;
#[derive(Debug)]
struct Handler{
    rc:Rc<u32>,
}

#[tokio::main]
async fn main(){
    let handler = Arc::new(Handler:{Rc::new(1)});

    let handler_cloned = Arc::clone(&handler);

    tokio::spawn(process(handler_cloned));
}

async fn process(handler: Arc<Handler>){}

三、Rust Future异步理解

Rust只提供Future机制,以及相应的async/await语法支持。而async runtime留给社区开发,什么是运行时?,可以查看下官方文档,又是一个嘴里只会说,但没有实质理解的概念。

Future与Async runtime的交互

  • Future 中的 poll 方法、Context、Poll enum是和 AsyncRumtime 交互的关键
    • Async runtime 通过 poll 方法让 Future 执行
    • Future 通过 Poll 告诉 Async runtime 执行情况
    • Future 通过 Context 告诉 Asyncruntime 自己已就绪
use tokio::time::sleep;
use std::{
    future::Future,
    pin::Pin,
    sync::{Arc, Mutex},
    task::{Context, Poll, Waker},
    thread,
    time::Duration,
};
#[tokio::main]
async fn main() {
    SleepFuture::new(Duration::from_secs(2)).await;
}

struct SleepFuture {
    duration: Duration,
    state:State,
}
struct State{
    waker:Option<Waker>,
    inner_state:InnerState,
}
#[derive(PartialEq)]
enum InnerState{
    Init,
    Sleeping,
    Done,
}

impl SleepFuture {
    fn new(duration: Duration) -> Self {
        Self { 
            duration,
            state:State{
                waker:None,
                inner_state:InnerState::Init,
            }
        }
    }
}

impl Future for SleepFuture{
    type Output = ();
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if self.state.inner_state == InnerState::Done{
            return Poll::Ready(())
        }
        if self.state.inner_state == InnerState::Init{
            self.state.waker = Some(cx.waker().clone());
            self.state.inner_state = InnerState::Sleeping;
            thread::spawn(move || {
                thread::sleep(self.duration);
                self.state.inner_state = InnerState::Done;
                if let Some(waker) = self.state.waker.take(){
                    // 通知异步运行时 重新再调用poll
                    waker.wake();
                }
            });
        }
        
        Poll::Pending
    }
}

修复

use std::{
    future::Future,
    pin::Pin,
    sync::{Arc, Mutex},
    task::{Context, Poll, Waker},
    thread,
    time::Duration,
};
#[tokio::main]
async fn main() {
    println!("start in main");
    SleepFuture::new(Duration::from_secs(2)).await;
    println!("end in main");

}

struct SleepFuture {
    duration: Duration,
    state:Arc<Mutex<State>>,
}
struct State{
    waker:Option<Waker>,
    inner_state:InnerState,
}
#[derive(PartialEq)]
enum InnerState{
    Init,
    Sleeping,
    Done,
}

impl SleepFuture {
    fn new(duration: Duration) -> Self {
        Self { 
            duration,
            state:Arc::new(Mutex::new(State{
                waker:None,
                inner_state:InnerState::Init,
            }))
        }
    }
}

impl Future for SleepFuture{
    type Output = ();
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
    
        let mut guard = self.state.lock().unwrap();
        println!("polling...");
        if guard.inner_state == InnerState::Done{
            return Poll::Ready(())
        }

        if guard.inner_state == InnerState::Init{
            guard.waker = Some(cx.waker().clone());
            guard.inner_state = InnerState::Sleeping;
            let duration = self.duration;
            let state_cloned = Arc::clone(&self.state);
        
            thread::spawn(move || {
                println!("start sleeping...");
                thread::sleep(duration);
                let mut guard = state_cloned.lock().unwrap();
                guard.inner_state = InnerState::Done;
                if let Some(waker) = guard.waker.take(){
                    waker.wake_by_ref();
                }
                println!("wake up");
            });
        }
        guard.waker = Some(cx.waker().clone());
        println!("pending...");
        Poll::Pending
    }
}

四、async/await机制理解

Rust async/await是语法糖,真正执行的还是Future+Poll

async方法生成一个Future,await则是进行poll轮询,可以将async理解成一个Future以及一个状态机

//Cargo.toml
    [dependencies]
    tokio = { version = "1.48.0", features = ["full"] }
    
//lib.rs:
use std::{
    future::Future,
    pin::Pin,
    sync::{Arc, Mutex},
    task::{Context, Poll, Waker},
    thread,
    time::Duration,
};

pub async fn sleep(duration: Duration){
    SleepFuture::new(duration).await;
}

pub struct SleepFuture {
    duration: Duration,
    state:Arc<Mutex<State>>,
}
struct State{
    waker:Option<Waker>,
    inner_state:InnerState,
}
#[derive(PartialEq)]
enum InnerState{
    Init,
    Sleeping,
    Done,
}

impl SleepFuture {
   pub fn new(duration: Duration) -> Self {
        Self { 
            duration,
            state:Arc::new(Mutex::new(State{
                waker:None,
                inner_state:InnerState::Init,
            }))
        }
    }
}

impl Future for SleepFuture{
    type Output = ();
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let mut guard = self.state.lock().unwrap();
        println!("polling...");
        if guard.inner_state == InnerState::Done{
            return Poll::Ready(())
        }
        if guard.inner_state == InnerState::Init{
            guard.waker = Some(cx.waker().clone());
            guard.inner_state = InnerState::Sleeping;
            let duration = self.duration;
            let state_cloned = Arc::clone(&self.state);
        
            thread::spawn(move || {
                println!("start sleeping...");
                thread::sleep(duration);
                let mut guard = state_cloned.lock().unwrap();
                guard.inner_state = InnerState::Done;
                if let Some(waker) = guard.waker.take(){
                    waker.wake_by_ref();
                }
                println!("wake up");
            });
        }
        guard.waker = Some(cx.waker().clone());
        println!("pending...");
        Poll::Pending
    }
}
// main.rs
//demo_test为创建的项目名称
use demo_test::sleep;
use std::time::Duration;
#[tokio::main]
async fn main() {
    let v = vec![1,2,3];
    let s = String::from("hello");

    foo(v,s).await;
    //FooFut::new(v,s).await;
}
// 这里执行的顺序大概是,先执行第一个println!
// 然后执行第一个sleep,该函数第一次状态可能是Poll::Pending
// 那么让出线程控制权
// 等第一个sleep结束后,再进入foo方法
// 开始执行第二个println!以及第二个sleep
// 然后又是先让出控制权,等Poll::Ready时,再进入foo函数
// 最后执行 返回42
async fn foo(v:Vec<u32>,s:String)->u32{
    println!("foo-v:{:?}",v);
    sleep(Duration::from_secs(2)).await;
    println!("foo-s:{}",s);
    sleep(Duration::from_secs(2)).await;
    42
}

//  async fn foo函数运行时大概逻辑
struct FooFut{
    state:FooFutState,
    v:Vec<u32>,
    s:String,
}
impl FooFut{
    fn new(v:Vec<u32>,s:String)->Self{
        Self{
            state:FooFutState::Init,
            v,
            s,
        }
    }
}
enum FooFutState{
    Init,
    Sleeping1(SleepFuture),
    Sleeping2(SleepFuture),
    Done,
}

impl Future for FooFut{
    type Output = u32;
    fn poll(mut self:Pin<&mut Self>,cx:&mut Context<'_>) -> Poll<Self::Output>{
        loop{
            match self.as_mut().get_mut().state{
                FooFutState::Init=>{
                    println!("foo-v:{:?}",self.v);
                    let fut1 = SleepFuture::new(Duration::from_secs(2));
                    self.as_mut().get_mut().state = FooFutState::Sleeping1(fut1);
                }
                FooFutState::Sleeping1(ref mut fut1)=>{
                    match Pin::new(fut1).poll(cx){
                        Poll::Ready(_)=>{
                            println!("foo-s:{}",self.s);
                            let fut2 = SleepFuture::new(Duration::from_secs(2));
                            self.as_mut().get_mut().state = FooFutState::Sleeping2(fut2);
                        }
                        Poll::Pending=>{
                            return Poll::Pending;
                        }

                    }
                }
                FooFutState::Sleeping2(ref mut fut2)=>{
                    match Pin::new(fut2).poll(cx){
                        Poll::Ready(_)=>{
                            self.as_mut().get_mut().state = FooFutState::Done;
                        }
                        Poll::Pending=>{
                            return Poll::Pending;
                        }

                    }
                }
                FooFutState::Done=>{
                    return Poll::Ready(42);
                }
            }
        }
    }
}



五、Pin机制理解

为什么需要PIn?
自引用结构
    可以由手工的自引用结构
    async生成的匿名Future也可能是自引用结构
自引用结构的内存不能被移动
    否则将带来内存安全问题
Pin的语义
    Pin主结构内存不被改变
    Pin<P\<T>>确保的是T被pin住,而非P
        如 对于Pin<&mut T>,pin住的是T,而不是&mut T
        如 对于Pin<Box\<T>>, pin住的是T,而不是Box\<T>
Unpin和!Unpin
    Pin与Unpin不是一对,Unpin和!Unpin才是一对
        可以把Pin理解成动词,Unpin理解成形容词
    Pin<P\<T>>对于T:Unpin不起作用,因为T是Unpinable,被Pin也没有意义

在使用Pin需要注意点是,当Pin住了元素,还要在源数据结构中添加_marker: PhantomPinned,来告诉数据不能再使用unPin的方法,否则比如通过get_mut仍能获取到unpin的数据,这样pin就没有意义了