深入淺出Rust Future Part-3

krircc發表於2019-01-19

譯自Rust futures: an uneducated, short and hopefully not boring tutorial – Part 3 – The reactor
本文時間:2018-12-03,譯者:
motecshine, 簡介:motecshine

歡迎向Rust中文社群投稿,投稿地址 ,好文將在以下地方直接展示

  1. Rust中文社群首頁
  2. Rust中文社群Rust文章欄目
  3. 知乎專欄Rust語言
  4. sf.gg專欄Rust語言
  5. 微博Rustlang-cn

Intro

在這篇文章中我們將會討論和闡釋Reactor是如何工作的.在上篇文章中我們,我們頻繁的使用Reactor來執行我們的Future,但是並沒有闡述它是如何工作的。現在是時候闡明它了。

Reactor? Loop?

如果用一句話來描述Reactor,那應該是:

Reactor是一個環(Loop)

舉個例子:
你決定通過Email邀請你喜歡的女孩或者男孩(emmm, 這個栗子聽起來很老套), 你懷著忐忑的心將這份郵件傳送出去,心裡焦急著等待著, 不停的一遍又一遍的檢查你的郵箱是否有新的回覆. 直到收到回覆。
Rust`s Reactor就是這樣, 你給他一個future, 他會不斷的檢查,直到這個future完成(或者返回錯誤). Reactor通過呼叫程式設計師實現的Poll函式,來檢查Future是否已完成。你所要做的就是實現future poll 並且返回Poll<T, E>結構。但是 Reactor也不會無休止的對你的future function輪詢。

A future from scratch

為了讓我們能更容易理解Reactor知識,我們還是從零開始實現一個Future. 換句話說就是,我們將動手實現Future Trait.

#[derive(Debug)]
struct WaitForIt {
    message: String,
    until: DateTime<Utc>,
    polls: u64,
}

我們的結構體欄位也很簡單:

  • message: 自定義字串訊息體
  • polls: 輪循次數
  • util: 等待時間

我們還會實現 WaitFotIt結構體的new方法.這個方法作用是初始化WaitForIt

impl WaitForIt {
    pub fn new(message: String, delay: Duration) -> WaitForIt {
        WaitForIt {
            polls: 0,
            message: message,
            until: Utc::now() + delay,
        }
    }
}

impl Future for WaitForIt {
    type Item = String;
    type Error = Box<Error>;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        let now = Utc::now();
        if self.until < now {
            Ok(Async::Ready(
                format!("{} after {} polls!", self.message, self.polls),
            ))
        } else {
            self.polls += 1;

            println!("not ready yet --> {:?}", self);
            Ok(Async::NotReady)
        }
    }
}

讓我們逐步解釋

    type Item = String;
    type Error = Box<Error>;

上面兩行在RUST裡被叫做associated types, 意思就是Future在將來完成時返回的值(或者錯誤).

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {}

定義輪詢的方法。Self::Item, Self::Error 是我們定義的associated types站位符。在我們的例子中,該方法如下:

fn poll(&mut self) - > Poll <String,Box <Error >>

現在看看我們的邏輯程式碼:

let now = Utc::now();
if self.until < now {
// 告訴reactor `Future` 已經完成了!
} else {
// 告訴 reactor `Future` 還沒準備好,過會兒再來。
}

Rust裡我們該怎樣告訴Reactor某個Future已經完成了?很簡單使用列舉

Ok(Async::NotReady(.......)) // 還沒完成
Ok(Async::Ready(......)) // 完成了

讓我們來實現上述的方法:

impl Future for WaitForIt {
    type Item = String;
    type Error = Box<Error>;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        let now = Utc::now();
        if self.until < now {
            Ok(Async::Ready(
                format!("{} after {} polls!", self.message, self.polls),
            ))
        } else {
            self.polls += 1;

            println!("not ready yet --> {:?}", self);
            Ok(Async::NotReady)
        }
    }
}

為了讓這段程式碼執行起來我們還需要:

extern crate chrono;
extern crate futures;

extern crate tokio_core;

use futures::done;
use futures::prelude::*;
use futures::future::{err, ok};
use tokio_core::reactor::Core;
use std::error::Error;
use futures::prelude::*;
use futures::*;
use chrono::prelude::*;
use chrono::*;

fn main() {
    let mut reactor = Core::new().unwrap();

    let wfi_1 = WaitForIt::new("I`m done:".to_owned(), Duration::seconds(1));
    println!("wfi_1 == {:?}", wfi_1);

    let ret = reactor.run(wfi_1).unwrap();
    println!("ret == {:?}", ret);
}

執行!! 等待一秒我們將會看到結果:

Running `target/debug/tst_fut_create`
wfi_1 == WaitForIt { message: "I`m done:", until: 2017-11-07T16:07:06.382232234Z, polls: 0 }
not ready yet --> WaitForIt { message: "I`m done:", until: 2017-11-07T16:07:06.382232234Z, polls: 1 }

emmm~, 只執行一次就被卡住了, 但是沒有額外的消耗CPU.但是為什麼會這樣?

如果不明確告訴ReactorReactor是不會再次輪詢停放(park)給它的Future.

(- 譯註: Park: 翻譯成停放其實也挺好的,就像車場的停車位一樣.)

在我們的例子裡, Reactor會立即執行我們停放的Future方法, 當我們返回Async::NotReady, 它就會認為當前停放的Future還未完成。如果我們不主動去解除停放,Reactor永遠也不會再次呼叫。

空閒中的Reactor是不會消耗CPU的。這樣看起來Reactor效率還是很高的。
在我們的電子郵件示例中,我們可以避免手動檢查郵件並等待通知。 所以我們可以在此期間自由玩Doom。(emm~看來作者很喜歡這款遊戲).

另一個更有意義的示例可能是從網路接收資料。 我們可以阻止我們的執行緒等待網路資料包,或者我們等待時可以做其他事情。 您可能想知道為什麼這種方法比使用OS執行緒更好?

Unparking

我們該如何糾正我們例子?我們需要以某種方式取消我們的Future。 理想情況下,我們應該有一些外部事件來取消我們的Future(例如按鍵或網路資料包),但是對於我們的示例,我們將使用這個簡單的行手動取消停放

futures::task::current().notify();

像這樣:

impl Future for WaitForIt {
    type Item = String;
    type Error = Box<Error>;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        let now = Utc::now();
        if self.until < now {
            Ok(Async::Ready(
                format!("{} after {} polls!", self.message, self.polls),
            ))
        } else {
            self.polls += 1;

            println!("not ready yet --> {:?}", self);
            futures::task::current().notify();
            Ok(Async::NotReady)
        }
    }
}

現在程式碼完成了。 請注意,在我的情況下,該函式已被呼叫超過50k次, CPU佔用也很高!
這是嚴重的浪費,也清楚地說明你為什麼需要在某個合理的時間點去Unpark Future.( That`s a waste of resources and clearly demonstrates why you should unpark your future only when something happened. )

另請注意迴圈如何僅消耗單個執行緒。 這是設計和效率的來源之一。 當然,如果需要,您可以使用更多執行緒。

Joining

Reactor可以同時執行多個Future,這也是他為什麼如此有https://github.com/rustlang-c… 那麼我們該如何充分利用單執行緒: 當一個Future被停放的時候, 另一個可以繼續工作。

對於這個例子,我們將重用我們的WaitForIt結構。 我們只是同時呼叫兩次。 我們開始建立兩個Future的例項:

let wfi_1 = WaitForIt::new("I`m done:".to_owned(), Duration::seconds(1));
println!("wfi_1 == {:?}", wfi_1);
let wfi_2 = WaitForIt::new("I`m done too:".to_owned(), Duration::seconds(1));
println!("wfi_2 == {:?}", wfi_2);

現在我們來呼叫futures::future::join_all, 他需要一個vec![]迭代器, 並且返回列舉過的Future

let v = vec![wfi_1, wfi_2];
let sel = join_all(v);

我們重新實現的程式碼像這樣:

fn main() {
    let mut reactor = Core::new().unwrap();

    let wfi_1 = WaitForIt::new("I`m done:".to_owned(), Duration::seconds(1));
    println!("wfi_1 == {:?}", wfi_1);
    let wfi_2 = WaitForIt::new("I`m done too:".to_owned(), Duration::seconds(1));
    println!("wfi_2 == {:?}", wfi_2);

    let v = vec![wfi_1, wfi_2];

    let sel = join_all(v);

    let ret = reactor.run(sel).unwrap();
    println!("ret == {:?}", ret);
}

這裡的關鍵點是兩個請求是交錯的:第一個Future被呼叫,然後是第二個,然後是第一個,依此類推,直到兩個完成。 如上圖所示,第一個Future在第二個之前完成。 第二個在完成之前被呼叫兩次。

Select

Future的特性還有很多功能。 這裡值得探討的另一件事是select函式。 select函式執行兩個(或者在select_all的情況下更多)Future,並返回第一個完成。 這對於實現超時很有用。 我們的例子可以簡單:

fn main() {
    let mut reactor = Core::new().unwrap();

    let wfi_1 = WaitForIt::new("I`m done:".to_owned(), Duration::seconds(1));
    println!("wfi_1 == {:?}", wfi_1);
    let wfi_2 = WaitForIt::new("I`m done too:".to_owned(), Duration::seconds(2));
    println!("wfi_2 == {:?}", wfi_2);

    let v = vec![wfi_1, wfi_2];

    let sel = select_all(v);

    let ret = reactor.run(sel).unwrap();
    println!("ret == {:?}", ret);
}

Closing remarks

下篇將會建立一個更RealFuture.

可執行的程式碼

extern crate chrono;
extern crate futures;

extern crate tokio_core;

use futures::done;
use futures::prelude::*;
use futures::future::{err, ok};
use tokio_core::reactor::Core;
use std::error::Error;
use futures::prelude::*;
use futures::*;
use chrono::prelude::*;
use chrono::*;
use futures::future::join_all;
#[derive(Debug)]
struct WaitForIt {
    message: String,
    until: DateTime<Utc>,
    polls: u64,
}

impl WaitForIt {
    pub fn new(message: String, delay: Duration) -> WaitForIt {
        WaitForIt {
            polls: 0,
            message: message,
            until: Utc::now() + delay,
        }
    }
}

iml Future for WaitForIt {
    type Item = String;
    type Error = Box<Error>;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        let now = Utc::now();
        if self.until < now {
            Ok(Async::Ready(
                format!("{} after {} polls!", self.message, self.polls),
            ))
        } else {
            self.polls += 1;

            println!("not ready yet --> {:?}", self);
            futures::task::current().notify();
            Ok(Async::NotReady)
        }
    }
}

fn main() {
    let mut reactor = Core::new().unwrap();

    let wfi_1 = WaitForIt::new("I`m done:".to_owned(), Duration::seconds(1));
    println!("wfi_1 == {:?}", wfi_1);
    let wfi_2 = WaitForIt::new("I`m done too:".to_owned(), Duration::seconds(1));
    println!("wfi_2 == {:?}", wfi_2);

    let v = vec![wfi_1, wfi_2];

    let sel = join_all(v);

    let ret = reactor.run(sel).unwrap();
    println!("ret == {:?}", ret);
}