譯自Rust futures: an uneducated, short and hopefully not boring tutorial – Part 3 – The reactor
本文時間:2018-12-03,譯者:
motecshine, 簡介:motecshine
歡迎向Rust中文社群投稿,投稿地址 ,好文將在以下地方直接展示
- Rust中文社群首頁
- Rust中文社群Rust文章欄目
- 知乎專欄Rust語言
- sf.gg專欄Rust語言
- 微博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
.但是為什麼會這樣?
如果不明確告訴
Reactor
,Reactor
是不會再次輪詢停放(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
下篇將會建立一個更Real
的Future
.
可執行的程式碼
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);
}