天天看點

005 Rust異步程式設計,Pin介紹

為了對Future調用poll,需要使用到Pin的特殊類型。本節就介紹一下Pin類型。

異步背後的一些原理

例子1

  • 源碼
//檔案src/main.rs
use futures::executor;

async fn async_function1() {
    println!("async function1 ++++ !");
}

async fn async_function2() {
    println!("async function2 ++++ !");
}

async fn async_main() {
    let f1 = async_function1();
    let f2 = async_function2();
    
    //重點關注這裡---------
    let f = async move {
        f1.await;
        f2.await; 
    };
    //---------------------
    f.await;
}

fn main() {
    executor::block_on(async_main());
}      
  • 配置,在Cargo.toml中添加
[dependencies]
futures = "0.3.4      

我們主要考慮async_main()函數中的async塊(async函數也是一樣,通過async都是轉化為Future),實際背後會做如下工作:

(1)為async塊生成一個類似于如下的結構體:

struct AsyncFuture {
    fut_one: FutFunction1,
    fut_two: FutFunction2,
    state: State,
}

//state的定義可能如下
enum State {
    AwaitingFutFunction1,
    AwaitingFutFunction2,
    Done,
}      

(2)為其生成對應的poll函數:

impl Future for AsyncFuture {
    type Output = ();

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
        loop {
            match self.state {
                State::AwaitingFutFunction1 => match self.fut_one.poll(..) {
                    Poll::Ready(()) => self.state = State::AwaitingFutFunction2,
                    Poll::Pending => return Poll::Pending,
                }
                State::AwaitingFutFunction2 => match self.fut_two.poll(..) {
                    Poll::Ready(()) => self.state = State::Done,
                    Poll::Pending => return Poll::Pending,
                }
                State::Done => return Poll::Ready(()),
            }
        }
    }
}      

好,到這裡,我們隻是模拟編譯器給async代碼塊進行了展開,那麼和我們要講的Pin有什麼關系呢?

例子2

我們再考慮如下例子:

async fn async_put_data_to_buf(mut buf: &[u8]) {
    //to do
    ...
}

async fn async_main () {
    //重點關注這裡---------
    let f = async {
        let mut x = [0; 128];
        let read_into_buf_fut = async_put_data_to_buf(&mut x);
        read_into_buf_fut.await;
    };
    //---------------------
    
    f.await;
}      

對于上面async_main中的async塊,編譯器為其生成的結構如下:

struct ReadIntoBuf<'a> {
    buf: &'a mut [u8], // points to `x` below
}

struct AsyncFuture {
    x: [u8; 128], 
    read_into_buf_fut: ReadIntoBuf<'what_lifetime?>,
}      

在AsyncFuture中,read_into_buf_fut.buf指向x,相當于是一個自引用(一個字段引用另一個字段)。但是如果AsyncFuture發生移動,x肯定也會發生移動,如果read_into_buf_fut.buf還是指向原來的值的話,則會變成無效。

而Pin就是為了解決此問題的。

Pin介紹

Pin類型包着指針類型,保證指針背後的值将不被移動。例如 Pin<&mut T>,Pin<&T>, Pin<Box> 都保證 T 不會移動。

拿上面的例子來說,如果使用Pin<>就是将x對應的那塊記憶體固定,這樣即使AsyncFuture發生移動,但是x不會移動,那麼read_into_buf_fut.buf不會變成懸垂引用。

use pin_utils::pin_mut; // `pin_utils` is a handy crate available on crates.io

// A function which takes a `Future` that implements `Unpin`.
fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { /* ... */ }

let fut = async { /* ... */ };
execute_unpin_future(fut); // Error: `fut` does not implement `Unpin` trait

// Pinning with `Box`:
let fut = async { /* ... */ };
let fut = Box::pin(fut);
execute_unpin_future(fut); // OK

// Pinning with `pin_mut!`:
let fut = async { /* ... */ };
pin_mut!(fut);
execute_unpin_future(fut); // OK      

繼續閱讀