2018/深入淺出Rust-Future-Part-3

譯自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-cn/forum/edit/master/translate/futures/深入淺出Rust-Future-Part-3.md效率的原因. 那麼我們該如何充分利用單線程: 當一個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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章